aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/utility
diff options
context:
space:
mode:
Diffstat (limited to 'webrtc/modules/utility')
-rw-r--r--webrtc/modules/utility/BUILD.gn48
-rw-r--r--webrtc/modules/utility/OWNERS4
-rw-r--r--webrtc/modules/utility/interface/audio_frame_operations.h58
-rw-r--r--webrtc/modules/utility/interface/file_player.h111
-rw-r--r--webrtc/modules/utility/interface/file_recorder.h84
-rw-r--r--webrtc/modules/utility/interface/helpers_android.h87
-rw-r--r--webrtc/modules/utility/interface/helpers_ios.h59
-rw-r--r--webrtc/modules/utility/interface/jvm_android.h185
-rw-r--r--webrtc/modules/utility/interface/mock/mock_process_thread.h38
-rw-r--r--webrtc/modules/utility/interface/process_thread.h66
-rw-r--r--webrtc/modules/utility/source/OWNERS5
-rw-r--r--webrtc/modules/utility/source/audio_frame_operations.cc109
-rw-r--r--webrtc/modules/utility/source/audio_frame_operations_unittest.cc225
-rw-r--r--webrtc/modules/utility/source/coder.cc112
-rw-r--r--webrtc/modules/utility/source/coder.h61
-rw-r--r--webrtc/modules/utility/source/file_player_impl.cc402
-rw-r--r--webrtc/modules/utility/source/file_player_impl.h79
-rw-r--r--webrtc/modules/utility/source/file_player_unittests.cc107
-rw-r--r--webrtc/modules/utility/source/file_recorder_impl.cc261
-rw-r--r--webrtc/modules/utility/source/file_recorder_impl.h93
-rw-r--r--webrtc/modules/utility/source/helpers_android.cc123
-rw-r--r--webrtc/modules/utility/source/helpers_ios.mm182
-rw-r--r--webrtc/modules/utility/source/jvm_android.cc264
-rw-r--r--webrtc/modules/utility/source/process_thread_impl.cc237
-rw-r--r--webrtc/modules/utility/source/process_thread_impl.h84
-rw-r--r--webrtc/modules/utility/source/process_thread_impl_unittest.cc304
-rw-r--r--webrtc/modules/utility/utility.gypi43
27 files changed, 3431 insertions, 0 deletions
diff --git a/webrtc/modules/utility/BUILD.gn b/webrtc/modules/utility/BUILD.gn
new file mode 100644
index 0000000000..163515c466
--- /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/jvm_android.h",
+ "interface/process_thread.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/jvm_android.cc",
+ "source/process_thread_impl.cc",
+ "source/process_thread_impl.h",
+ ]
+
+ configs += [ "../..:common_config" ]
+ public_configs = [ "../..:common_inherited_config" ]
+
+ if (is_clang) {
+ # Suppress warnings from Chrome's Clang plugins.
+ # See http://code.google.com/p/webrtc/issues/detail?id=163 for details.
+ configs -= [ "//build/config/clang:find_bad_constructs" ]
+ }
+
+ deps = [
+ "../..:webrtc_common",
+ "../../common_audio",
+ "../../system_wrappers",
+ "../audio_coding",
+ "../media_file",
+ ]
+}
diff --git a/webrtc/modules/utility/OWNERS b/webrtc/modules/utility/OWNERS
new file mode 100644
index 0000000000..347d278614
--- /dev/null
+++ b/webrtc/modules/utility/OWNERS
@@ -0,0 +1,4 @@
+asapersson@webrtc.org
+perkj@webrtc.org
+
+per-file BUILD.gn=kjellander@webrtc.org
diff --git a/webrtc/modules/utility/interface/audio_frame_operations.h b/webrtc/modules/utility/interface/audio_frame_operations.h
new file mode 100644
index 0000000000..c2af68ab1b
--- /dev/null
+++ b/webrtc/modules/utility/interface/audio_frame_operations.h
@@ -0,0 +1,58 @@
+/*
+ * 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_VOICE_ENGINE_AUDIO_FRAME_OPERATIONS_H_
+#define WEBRTC_VOICE_ENGINE_AUDIO_FRAME_OPERATIONS_H_
+
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+class AudioFrame;
+
+// TODO(andrew): consolidate this with utility.h and audio_frame_manipulator.h.
+// Change reference parameters to pointers. Consider using a namespace rather
+// than a class.
+class AudioFrameOperations {
+ public:
+ // Upmixes mono |src_audio| to stereo |dst_audio|. This is an out-of-place
+ // operation, meaning src_audio and dst_audio must point to different
+ // buffers. It is the caller's responsibility to ensure that |dst_audio| is
+ // sufficiently large.
+ static void MonoToStereo(const int16_t* src_audio, size_t samples_per_channel,
+ int16_t* dst_audio);
+ // |frame.num_channels_| will be updated. This version checks for sufficient
+ // buffer size and that |num_channels_| is mono.
+ static int MonoToStereo(AudioFrame* frame);
+
+ // Downmixes stereo |src_audio| to mono |dst_audio|. This is an in-place
+ // operation, meaning |src_audio| and |dst_audio| may point to the same
+ // buffer.
+ static void StereoToMono(const int16_t* src_audio, size_t samples_per_channel,
+ int16_t* dst_audio);
+ // |frame.num_channels_| will be updated. This version checks that
+ // |num_channels_| is stereo.
+ static int StereoToMono(AudioFrame* frame);
+
+ // Swap the left and right channels of |frame|. Fails silently if |frame| is
+ // not stereo.
+ static void SwapStereoChannels(AudioFrame* frame);
+
+ // Zeros out the audio and sets |frame.energy| to zero.
+ static void Mute(AudioFrame& frame);
+
+ static int Scale(float left, float right, AudioFrame& frame);
+
+ static int ScaleWithSat(float scale, AudioFrame& frame);
+};
+
+} // namespace webrtc
+
+#endif // #ifndef WEBRTC_VOICE_ENGINE_AUDIO_FRAME_OPERATIONS_H_
diff --git a/webrtc/modules/utility/interface/file_player.h b/webrtc/modules/utility/interface/file_player.h
new file mode 100644
index 0000000000..44f03e475a
--- /dev/null
+++ b/webrtc/modules/utility/interface/file_player.h
@@ -0,0 +1,111 @@
+/*
+ * 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_UTILITY_INTERFACE_FILE_PLAYER_H_
+#define WEBRTC_MODULES_UTILITY_INTERFACE_FILE_PLAYER_H_
+
+#include "webrtc/common_types.h"
+#include "webrtc/engine_configurations.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/typedefs.h"
+#include "webrtc/video_frame.h"
+
+namespace webrtc {
+class FileCallback;
+
+class FilePlayer
+{
+public:
+ // The largest decoded frame size in samples (60ms with 32kHz sample rate).
+ enum {MAX_AUDIO_BUFFER_IN_SAMPLES = 60*32};
+ enum {MAX_AUDIO_BUFFER_IN_BYTES = MAX_AUDIO_BUFFER_IN_SAMPLES*2};
+
+ // Note: will return NULL for unsupported formats.
+ static FilePlayer* CreateFilePlayer(const uint32_t instanceID,
+ const FileFormats fileFormat);
+
+ static void DestroyFilePlayer(FilePlayer* player);
+
+ // Read 10 ms of audio at |frequencyInHz| to |outBuffer|. |lengthInSamples|
+ // will be set to the number of samples read (not the number of samples per
+ // channel).
+ virtual int Get10msAudioFromFile(
+ int16_t* outBuffer,
+ size_t& lengthInSamples,
+ int frequencyInHz) = 0;
+
+ // Register callback for receiving file playing notifications.
+ virtual int32_t RegisterModuleFileCallback(
+ FileCallback* callback) = 0;
+
+ // API for playing audio from fileName to channel.
+ // Note: codecInst is used for pre-encoded files.
+ virtual int32_t StartPlayingFile(
+ const char* fileName,
+ bool loop,
+ uint32_t startPosition,
+ float volumeScaling,
+ uint32_t notification,
+ uint32_t stopPosition = 0,
+ const CodecInst* codecInst = NULL) = 0;
+
+ // Note: codecInst is used for pre-encoded files.
+ virtual int32_t StartPlayingFile(
+ InStream& sourceStream,
+ uint32_t startPosition,
+ float volumeScaling,
+ uint32_t notification,
+ uint32_t stopPosition = 0,
+ const CodecInst* codecInst = NULL) = 0;
+
+ virtual int32_t StopPlayingFile() = 0;
+
+ virtual bool IsPlayingFile() const = 0;
+
+ virtual int32_t GetPlayoutPosition(uint32_t& durationMs) = 0;
+
+ // Set audioCodec to the currently used audio codec.
+ virtual int32_t AudioCodec(CodecInst& audioCodec) const = 0;
+
+ virtual int32_t Frequency() const = 0;
+
+ // Note: scaleFactor is in the range [0.0 - 2.0]
+ virtual int32_t SetAudioScaling(float scaleFactor) = 0;
+
+ // Return the time in ms until next video frame should be pulled (by
+ // calling GetVideoFromFile(..)).
+ // Note: this API reads one video frame from file. This means that it should
+ // be called exactly once per GetVideoFromFile(..) API call.
+ virtual int32_t TimeUntilNextVideoFrame() { return -1;}
+
+ virtual int32_t StartPlayingVideoFile(
+ const char* /*fileName*/,
+ bool /*loop*/,
+ bool /*videoOnly*/) { return -1;}
+
+ virtual int32_t video_codec_info(VideoCodec& /*videoCodec*/) const
+ {return -1;}
+
+ virtual int32_t GetVideoFromFile(VideoFrame& /*videoFrame*/) { return -1; }
+
+ // Same as GetVideoFromFile(). videoFrame will have the resolution specified
+ // by the width outWidth and height outHeight in pixels.
+ virtual int32_t GetVideoFromFile(VideoFrame& /*videoFrame*/,
+ const uint32_t /*outWidth*/,
+ const uint32_t /*outHeight*/) {
+ return -1;
+ }
+
+protected:
+ virtual ~FilePlayer() {}
+
+};
+} // namespace webrtc
+#endif // WEBRTC_MODULES_UTILITY_INTERFACE_FILE_PLAYER_H_
diff --git a/webrtc/modules/utility/interface/file_recorder.h b/webrtc/modules/utility/interface/file_recorder.h
new file mode 100644
index 0000000000..f2ce785368
--- /dev/null
+++ b/webrtc/modules/utility/interface/file_recorder.h
@@ -0,0 +1,84 @@
+/*
+ * 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_UTILITY_INTERFACE_FILE_RECORDER_H_
+#define WEBRTC_MODULES_UTILITY_INTERFACE_FILE_RECORDER_H_
+
+#include "webrtc/common_types.h"
+#include "webrtc/engine_configurations.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/media_file/interface/media_file_defines.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+#include "webrtc/typedefs.h"
+#include "webrtc/video_frame.h"
+
+namespace webrtc {
+
+class FileRecorder
+{
+public:
+
+ // Note: will return NULL for unsupported formats.
+ static FileRecorder* CreateFileRecorder(const uint32_t instanceID,
+ const FileFormats fileFormat);
+
+ static void DestroyFileRecorder(FileRecorder* recorder);
+
+ virtual int32_t RegisterModuleFileCallback(
+ FileCallback* callback) = 0;
+
+ virtual FileFormats RecordingFileFormat() const = 0;
+
+ virtual int32_t StartRecordingAudioFile(
+ const char* fileName,
+ const CodecInst& codecInst,
+ uint32_t notification) = 0;
+
+ virtual int32_t StartRecordingAudioFile(
+ OutStream& destStream,
+ const CodecInst& codecInst,
+ uint32_t notification) = 0;
+
+ // Stop recording.
+ // Note: this API is for both audio and video.
+ virtual int32_t StopRecording() = 0;
+
+ // Return true if recording.
+ // Note: this API is for both audio and video.
+ virtual bool IsRecording() const = 0;
+
+ virtual int32_t codec_info(CodecInst& codecInst) const = 0;
+
+ // Write frame to file. Frame should contain 10ms of un-ecoded audio data.
+ virtual int32_t RecordAudioToFile(
+ const AudioFrame& frame,
+ const TickTime* playoutTS = NULL) = 0;
+
+ // Open/create the file specified by fileName for writing audio/video data
+ // (relative path is allowed). audioCodecInst specifies the encoding of the
+ // audio data. videoCodecInst specifies the encoding of the video data.
+ // Only video data will be recorded if videoOnly is true. amrFormat
+ // specifies the amr/amrwb storage format.
+ // Note: the file format is AVI.
+ virtual int32_t StartRecordingVideoFile(
+ const char* fileName,
+ const CodecInst& audioCodecInst,
+ const VideoCodec& videoCodecInst,
+ bool videoOnly = false) = 0;
+
+ // Record the video frame in videoFrame to AVI file.
+ virtual int32_t RecordVideoToFile(const VideoFrame& videoFrame) = 0;
+
+protected:
+ virtual ~FileRecorder() {}
+
+};
+} // namespace webrtc
+#endif // WEBRTC_MODULES_UTILITY_INTERFACE_FILE_RECORDER_H_
diff --git a/webrtc/modules/utility/interface/helpers_android.h b/webrtc/modules/utility/interface/helpers_android.h
new file mode 100644
index 0000000000..5c73fe4566
--- /dev/null
+++ b/webrtc/modules/utility/interface/helpers_android.h
@@ -0,0 +1,87 @@
+/*
+ * 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_MODULES_UTILITY_INTERFACE_HELPERS_ANDROID_H_
+#define WEBRTC_MODULES_UTILITY_INTERFACE_HELPERS_ANDROID_H_
+
+#include <jni.h>
+#include <string>
+
+// Abort the process if |jni| has a Java exception pending.
+// TODO(henrika): merge with CHECK_JNI_EXCEPTION() in jni_helpers.h.
+#define CHECK_EXCEPTION(jni) \
+ RTC_CHECK(!jni->ExceptionCheck()) \
+ << (jni->ExceptionDescribe(), jni->ExceptionClear(), "")
+
+namespace webrtc {
+
+// Return a |JNIEnv*| usable on this thread or NULL if this thread is detached.
+JNIEnv* GetEnv(JavaVM* jvm);
+
+// Return a |jlong| that will correctly convert back to |ptr|. This is needed
+// because the alternative (of silently passing a 32-bit pointer to a vararg
+// function expecting a 64-bit param) picks up garbage in the high 32 bits.
+jlong PointerTojlong(void* ptr);
+
+// JNIEnv-helper methods that wraps the API which uses the JNI interface
+// pointer (JNIEnv*). It allows us to RTC_CHECK success and that no Java
+// exception is thrown while calling the method.
+jmethodID GetMethodID(
+ JNIEnv* jni, jclass c, const char* name, const char* signature);
+
+jmethodID GetStaticMethodID(
+ JNIEnv* jni, jclass c, const char* name, const char* signature);
+
+jclass FindClass(JNIEnv* jni, const char* name);
+
+jobject NewGlobalRef(JNIEnv* jni, jobject o);
+
+void DeleteGlobalRef(JNIEnv* jni, jobject o);
+
+// Return thread ID as a string.
+std::string GetThreadId();
+
+// Return thread ID as string suitable for debug logging.
+std::string GetThreadInfo();
+
+// Attach thread to JVM if necessary and detach at scope end if originally
+// attached.
+class AttachThreadScoped {
+ public:
+ explicit AttachThreadScoped(JavaVM* jvm);
+ ~AttachThreadScoped();
+ JNIEnv* env();
+
+ private:
+ bool attached_;
+ JavaVM* jvm_;
+ JNIEnv* env_;
+};
+
+// Scoped holder for global Java refs.
+template<class T> // T is jclass, jobject, jintArray, etc.
+class ScopedGlobalRef {
+ public:
+ ScopedGlobalRef(JNIEnv* jni, T obj)
+ : jni_(jni), obj_(static_cast<T>(NewGlobalRef(jni, obj))) {}
+ ~ScopedGlobalRef() {
+ DeleteGlobalRef(jni_, obj_);
+ }
+ T operator*() const {
+ return obj_;
+ }
+ private:
+ JNIEnv* jni_;
+ T obj_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_UTILITY_INTERFACE_HELPERS_ANDROID_H_
diff --git a/webrtc/modules/utility/interface/helpers_ios.h b/webrtc/modules/utility/interface/helpers_ios.h
new file mode 100644
index 0000000000..a5edee0279
--- /dev/null
+++ b/webrtc/modules/utility/interface/helpers_ios.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can 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_HELPERS_IOS_H_
+#define WEBRTC_MODULES_UTILITY_INTERFACE_HELPERS_IOS_H_
+
+#if defined(WEBRTC_IOS)
+
+#include <string>
+
+namespace webrtc {
+namespace ios {
+
+bool CheckAndLogError(BOOL success, NSError* error);
+
+std::string StdStringFromNSString(NSString* nsString);
+
+// Return thread ID as a string.
+std::string GetThreadId();
+
+// Return thread ID as string suitable for debug logging.
+std::string GetThreadInfo();
+
+// Returns [NSThread currentThread] description as string.
+// Example: <NSThread: 0x170066d80>{number = 1, name = main}
+std::string GetCurrentThreadDescription();
+
+std::string GetAudioSessionCategory();
+
+// Returns the current name of the operating system.
+std::string GetSystemName();
+
+// Returns the current version of the operating system.
+std::string GetSystemVersion();
+
+// Returns the version of the operating system as a floating point value.
+float GetSystemVersionAsFloat();
+
+// Returns the device type.
+// Examples: ”iPhone” and ”iPod touch”.
+std::string GetDeviceType();
+
+// Returns a more detailed device name.
+// Examples: "iPhone 5s (GSM)" and "iPhone 6 Plus".
+std::string GetDeviceName();
+
+} // namespace ios
+} // namespace webrtc
+
+#endif // defined(WEBRTC_IOS)
+
+#endif // WEBRTC_MODULES_UTILITY_INTERFACE_HELPERS_IOS_H_
diff --git a/webrtc/modules/utility/interface/jvm_android.h b/webrtc/modules/utility/interface/jvm_android.h
new file mode 100644
index 0000000000..0744fdbf12
--- /dev/null
+++ b/webrtc/modules/utility/interface/jvm_android.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can 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_SOURCE_JVM_H_
+#define WEBRTC_MODULES_UTILITY_SOURCE_JVM_H_
+
+#include <jni.h>
+#include <string>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/thread_checker.h"
+#include "webrtc/modules/utility/interface/helpers_android.h"
+
+namespace webrtc {
+
+// The JNI interface pointer (JNIEnv) is valid only in the current thread.
+// Should another thread need to access the Java VM, it must first call
+// AttachCurrentThread() to attach itself to the VM and obtain a JNI interface
+// pointer. The native thread remains attached to the VM until it calls
+// DetachCurrentThread() to detach.
+class AttachCurrentThreadIfNeeded {
+ public:
+ AttachCurrentThreadIfNeeded();
+ ~AttachCurrentThreadIfNeeded();
+
+ private:
+ rtc::ThreadChecker thread_checker_;
+ bool attached_;
+};
+
+// This class is created by the NativeRegistration class and is used to wrap
+// the actual Java object handle (jobject) on which we can call methods from
+// C++ in to Java. See example in JVM for more details.
+// TODO(henrika): extend support for type of function calls.
+class GlobalRef {
+ public:
+ GlobalRef(JNIEnv* jni, jobject object);
+ ~GlobalRef();
+
+ jboolean CallBooleanMethod(jmethodID methodID, ...);
+ jint CallIntMethod(jmethodID methodID, ...);
+ void CallVoidMethod(jmethodID methodID, ...);
+
+ private:
+ JNIEnv* const jni_;
+ const jobject j_object_;
+};
+
+// Wraps the jclass object on which we can call GetMethodId() functions to
+// query method IDs.
+class JavaClass {
+ public:
+ JavaClass(JNIEnv* jni, jclass clazz) : jni_(jni), j_class_(clazz) {}
+ ~JavaClass() {}
+
+ jmethodID GetMethodId(const char* name, const char* signature);
+ jmethodID GetStaticMethodId(const char* name, const char* signature);
+ jobject CallStaticObjectMethod(jmethodID methodID, ...);
+
+ protected:
+ JNIEnv* const jni_;
+ jclass const j_class_;
+};
+
+// Adds support of the NewObject factory method to the JavaClass class.
+// See example in JVM for more details on how to use it.
+class NativeRegistration : public JavaClass {
+ public:
+ NativeRegistration(JNIEnv* jni, jclass clazz);
+ ~NativeRegistration();
+
+ rtc::scoped_ptr<GlobalRef> NewObject(
+ const char* name, const char* signature, ...);
+
+ private:
+ JNIEnv* const jni_;
+};
+
+// This class is created by the JVM class and is used to expose methods that
+// needs the JNI interface pointer but its main purpose is to create a
+// NativeRegistration object given name of a Java class and a list of native
+// methods. See example in JVM for more details.
+class JNIEnvironment {
+ public:
+ explicit JNIEnvironment(JNIEnv* jni);
+ ~JNIEnvironment();
+
+ // Registers native methods with the Java class specified by |name|.
+ // Note that the class name must be one of the names in the static
+ // |loaded_classes| array defined in jvm_android.cc.
+ // This method must be called on the construction thread.
+ rtc::scoped_ptr<NativeRegistration> RegisterNatives(
+ const char* name, const JNINativeMethod *methods, int num_methods);
+
+ // Converts from Java string to std::string.
+ // This method must be called on the construction thread.
+ std::string JavaToStdString(const jstring& j_string);
+
+ private:
+ rtc::ThreadChecker thread_checker_;
+ JNIEnv* const jni_;
+};
+
+// Main class for working with Java from C++ using JNI in WebRTC.
+//
+// Example usage:
+//
+// // At initialization (e.g. in JNI_OnLoad), call JVM::Initialize.
+// JNIEnv* jni = ::base::android::AttachCurrentThread();
+// JavaVM* jvm = NULL;
+// jni->GetJavaVM(&jvm);
+// jobject context = ::base::android::GetApplicationContext();
+// webrtc::JVM::Initialize(jvm, context);
+//
+// // Header (.h) file of example class called User.
+// rtc::scoped_ptr<JNIEnvironment> env;
+// rtc::scoped_ptr<NativeRegistration> reg;
+// rtc::scoped_ptr<GlobalRef> obj;
+//
+// // Construction (in .cc file) of User class.
+// User::User() {
+// // Calling thread must be attached to the JVM.
+// env = JVM::GetInstance()->environment();
+// reg = env->RegisterNatives("org/webrtc/WebRtcTest", ,);
+// obj = reg->NewObject("<init>", ,);
+// }
+//
+// // Each User method can now use |reg| and |obj| and call Java functions
+// // in WebRtcTest.java, e.g. boolean init() {}.
+// bool User::Foo() {
+// jmethodID id = reg->GetMethodId("init", "()Z");
+// return obj->CallBooleanMethod(id);
+// }
+//
+// // And finally, e.g. in JNI_OnUnLoad, call JVM::Uninitialize.
+// JVM::Uninitialize();
+class JVM {
+ public:
+ // Stores global handles to the Java VM interface and the application context.
+ // Should be called once on a thread that is attached to the JVM.
+ static void Initialize(JavaVM* jvm, jobject context);
+ // Clears handles stored in Initialize(). Must be called on same thread as
+ // Initialize().
+ static void Uninitialize();
+ // Gives access to the global Java VM interface pointer, which then can be
+ // used to create a valid JNIEnvironment object or to get a JavaClass object.
+ static JVM* GetInstance();
+
+ // Creates a JNIEnvironment object.
+ // This method returns a NULL pointer if AttachCurrentThread() has not been
+ // called successfully. Use the AttachCurrentThreadIfNeeded class if needed.
+ rtc::scoped_ptr<JNIEnvironment> environment();
+
+ // Returns a JavaClass object given class |name|.
+ // Note that the class name must be one of the names in the static
+ // |loaded_classes| array defined in jvm_android.cc.
+ // This method must be called on the construction thread.
+ JavaClass GetClass(const char* name);
+
+ // TODO(henrika): can we make these private?
+ JavaVM* jvm() const { return jvm_; }
+ jobject context() const { return context_; }
+
+ protected:
+ JVM(JavaVM* jvm, jobject context);
+ ~JVM();
+
+ private:
+ JNIEnv* jni() const { return GetEnv(jvm_); }
+
+ rtc::ThreadChecker thread_checker_;
+ JavaVM* const jvm_;
+ jobject context_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_UTILITY_SOURCE_JVM_H_
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 0000000000..fd108a8354
--- /dev/null
+++ b/webrtc/modules/utility/interface/mock/mock_process_thread.h
@@ -0,0 +1,38 @@
+/*
+ * 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, void());
+ MOCK_METHOD0(Stop, void());
+ MOCK_METHOD1(WakeUp, void(Module* module));
+ MOCK_METHOD1(PostTask, void(ProcessTask* task));
+ MOCK_METHOD1(RegisterModule, void(Module* module));
+ MOCK_METHOD1(DeRegisterModule, void(Module* module));
+
+ // MOCK_METHOD1 gets confused with mocking this method, so we work around it
+ // by overriding the method from the interface and forwarding the call to a
+ // mocked, simpler method.
+ void PostTask(rtc::scoped_ptr<ProcessTask> task) override {
+ PostTask(task.get());
+ }
+};
+
+} // namespace webrtc
+#endif // WEBRTC_MODULES_UTILITY_INTERFACE_MOCK_PROCESS_THREAD_H_
diff --git a/webrtc/modules/utility/interface/process_thread.h b/webrtc/modules/utility/interface/process_thread.h
new file mode 100644
index 0000000000..451a5a301b
--- /dev/null
+++ b/webrtc/modules/utility/interface/process_thread.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_UTILITY_INTERFACE_PROCESS_THREAD_H_
+#define WEBRTC_MODULES_UTILITY_INTERFACE_PROCESS_THREAD_H_
+
+#include "webrtc/typedefs.h"
+#include "webrtc/base/scoped_ptr.h"
+
+namespace webrtc {
+class Module;
+
+class ProcessTask {
+ public:
+ ProcessTask() {}
+ virtual ~ProcessTask() {}
+
+ virtual void Run() = 0;
+};
+
+class ProcessThread {
+ public:
+ virtual ~ProcessThread();
+
+ static rtc::scoped_ptr<ProcessThread> Create(const char* thread_name);
+
+ // Starts the worker thread. Must be called from the construction thread.
+ virtual void Start() = 0;
+
+ // Stops the worker thread. Must be called from the construction thread.
+ virtual void Stop() = 0;
+
+ // Wakes the thread up to give a module a chance to do processing right
+ // away. This causes the worker thread to wake up and requery the specified
+ // module for when it should be called back. (Typically the module should
+ // return 0 from TimeUntilNextProcess on the worker thread at that point).
+ // Can be called on any thread.
+ virtual void WakeUp(Module* module) = 0;
+
+ // Queues a task object to run on the worker thread. Ownership of the
+ // task object is transferred to the ProcessThread and the object will
+ // either be deleted after running on the worker thread, or on the
+ // construction thread of the ProcessThread instance, if the task did not
+ // get a chance to run (e.g. posting the task while shutting down or when
+ // the thread never runs).
+ virtual void PostTask(rtc::scoped_ptr<ProcessTask> task) = 0;
+
+ // Adds a module that will start to receive callbacks on the worker thread.
+ // Can be called from any thread.
+ virtual void RegisterModule(Module* module) = 0;
+
+ // Removes a previously registered module.
+ // Can be called from any thread.
+ virtual void DeRegisterModule(Module* module) = 0;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_UTILITY_INTERFACE_PROCESS_THREAD_H_
diff --git a/webrtc/modules/utility/source/OWNERS b/webrtc/modules/utility/source/OWNERS
new file mode 100644
index 0000000000..3ee6b4bf5f
--- /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
new file mode 100644
index 0000000000..c07ca1fdf6
--- /dev/null
+++ b/webrtc/modules/utility/source/audio_frame_operations.cc
@@ -0,0 +1,109 @@
+/*
+ * 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/interface/module_common_types.h"
+#include "webrtc/modules/utility/interface/audio_frame_operations.h"
+
+namespace webrtc {
+
+void AudioFrameOperations::MonoToStereo(const int16_t* src_audio,
+ size_t samples_per_channel,
+ int16_t* dst_audio) {
+ for (size_t i = 0; i < samples_per_channel; i++) {
+ dst_audio[2 * i] = src_audio[i];
+ dst_audio[2 * i + 1] = src_audio[i];
+ }
+}
+
+int AudioFrameOperations::MonoToStereo(AudioFrame* frame) {
+ if (frame->num_channels_ != 1) {
+ return -1;
+ }
+ if ((frame->samples_per_channel_ * 2) >= AudioFrame::kMaxDataSizeSamples) {
+ // Not enough memory to expand from mono to stereo.
+ return -1;
+ }
+
+ int16_t data_copy[AudioFrame::kMaxDataSizeSamples];
+ memcpy(data_copy, frame->data_,
+ sizeof(int16_t) * frame->samples_per_channel_);
+ MonoToStereo(data_copy, frame->samples_per_channel_, frame->data_);
+ frame->num_channels_ = 2;
+
+ return 0;
+}
+
+void AudioFrameOperations::StereoToMono(const int16_t* src_audio,
+ size_t samples_per_channel,
+ int16_t* dst_audio) {
+ for (size_t i = 0; i < samples_per_channel; i++) {
+ dst_audio[i] = (src_audio[2 * i] + src_audio[2 * i + 1]) >> 1;
+ }
+}
+
+int AudioFrameOperations::StereoToMono(AudioFrame* frame) {
+ if (frame->num_channels_ != 2) {
+ return -1;
+ }
+
+ StereoToMono(frame->data_, frame->samples_per_channel_, frame->data_);
+ frame->num_channels_ = 1;
+
+ return 0;
+}
+
+void AudioFrameOperations::SwapStereoChannels(AudioFrame* frame) {
+ if (frame->num_channels_ != 2) return;
+
+ for (size_t i = 0; i < frame->samples_per_channel_ * 2; i += 2) {
+ int16_t temp_data = frame->data_[i];
+ frame->data_[i] = frame->data_[i + 1];
+ frame->data_[i + 1] = temp_data;
+ }
+}
+
+void AudioFrameOperations::Mute(AudioFrame& frame) {
+ memset(frame.data_, 0, sizeof(int16_t) *
+ frame.samples_per_channel_ * frame.num_channels_);
+}
+
+int AudioFrameOperations::Scale(float left, float right, AudioFrame& frame) {
+ if (frame.num_channels_ != 2) {
+ return -1;
+ }
+
+ for (size_t i = 0; i < frame.samples_per_channel_; i++) {
+ frame.data_[2 * i] =
+ static_cast<int16_t>(left * frame.data_[2 * i]);
+ frame.data_[2 * i + 1] =
+ static_cast<int16_t>(right * frame.data_[2 * i + 1]);
+ }
+ return 0;
+}
+
+int AudioFrameOperations::ScaleWithSat(float scale, AudioFrame& frame) {
+ int32_t temp_data = 0;
+
+ // Ensure that the output result is saturated [-32768, +32767].
+ for (size_t i = 0; i < frame.samples_per_channel_ * frame.num_channels_;
+ i++) {
+ temp_data = static_cast<int32_t>(scale * frame.data_[i]);
+ if (temp_data < -32768) {
+ frame.data_[i] = -32768;
+ } else if (temp_data > 32767) {
+ frame.data_[i] = 32767;
+ } else {
+ frame.data_[i] = static_cast<int16_t>(temp_data);
+ }
+ }
+ return 0;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/utility/source/audio_frame_operations_unittest.cc b/webrtc/modules/utility/source/audio_frame_operations_unittest.cc
new file mode 100644
index 0000000000..c278cdddcd
--- /dev/null
+++ b/webrtc/modules/utility/source/audio_frame_operations_unittest.cc
@@ -0,0 +1,225 @@
+/*
+ * 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 "testing/gtest/include/gtest/gtest.h"
+
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/utility/interface/audio_frame_operations.h"
+
+namespace webrtc {
+namespace {
+
+class AudioFrameOperationsTest : public ::testing::Test {
+ protected:
+ AudioFrameOperationsTest() {
+ // Set typical values.
+ frame_.samples_per_channel_ = 320;
+ frame_.num_channels_ = 2;
+ }
+
+ AudioFrame frame_;
+};
+
+void SetFrameData(AudioFrame* frame, int16_t left, int16_t right) {
+ for (size_t i = 0; i < frame->samples_per_channel_ * 2; i += 2) {
+ frame->data_[i] = left;
+ frame->data_[i + 1] = right;
+ }
+}
+
+void SetFrameData(AudioFrame* frame, int16_t data) {
+ for (size_t i = 0; i < frame->samples_per_channel_; i++) {
+ frame->data_[i] = data;
+ }
+}
+
+void VerifyFramesAreEqual(const AudioFrame& frame1, const AudioFrame& frame2) {
+ EXPECT_EQ(frame1.num_channels_, frame2.num_channels_);
+ EXPECT_EQ(frame1.samples_per_channel_,
+ frame2.samples_per_channel_);
+
+ for (size_t i = 0; i < frame1.samples_per_channel_ * frame1.num_channels_;
+ i++) {
+ EXPECT_EQ(frame1.data_[i], frame2.data_[i]);
+ }
+}
+
+TEST_F(AudioFrameOperationsTest, MonoToStereoFailsWithBadParameters) {
+ EXPECT_EQ(-1, AudioFrameOperations::MonoToStereo(&frame_));
+
+ frame_.samples_per_channel_ = AudioFrame::kMaxDataSizeSamples;
+ frame_.num_channels_ = 1;
+ EXPECT_EQ(-1, AudioFrameOperations::MonoToStereo(&frame_));
+}
+
+TEST_F(AudioFrameOperationsTest, MonoToStereoSucceeds) {
+ frame_.num_channels_ = 1;
+ SetFrameData(&frame_, 1);
+ AudioFrame temp_frame;
+ temp_frame.CopyFrom(frame_);
+ EXPECT_EQ(0, AudioFrameOperations::MonoToStereo(&frame_));
+
+ AudioFrame stereo_frame;
+ stereo_frame.samples_per_channel_ = 320;
+ stereo_frame.num_channels_ = 2;
+ SetFrameData(&stereo_frame, 1, 1);
+ VerifyFramesAreEqual(stereo_frame, frame_);
+
+ SetFrameData(&frame_, 0);
+ AudioFrameOperations::MonoToStereo(temp_frame.data_,
+ frame_.samples_per_channel_,
+ frame_.data_);
+ frame_.num_channels_ = 2; // Need to set manually.
+ VerifyFramesAreEqual(stereo_frame, frame_);
+}
+
+TEST_F(AudioFrameOperationsTest, StereoToMonoFailsWithBadParameters) {
+ frame_.num_channels_ = 1;
+ EXPECT_EQ(-1, AudioFrameOperations::StereoToMono(&frame_));
+}
+
+TEST_F(AudioFrameOperationsTest, StereoToMonoSucceeds) {
+ SetFrameData(&frame_, 4, 2);
+ AudioFrame temp_frame;
+ temp_frame.CopyFrom(frame_);
+ EXPECT_EQ(0, AudioFrameOperations::StereoToMono(&frame_));
+
+ AudioFrame mono_frame;
+ mono_frame.samples_per_channel_ = 320;
+ mono_frame.num_channels_ = 1;
+ SetFrameData(&mono_frame, 3);
+ VerifyFramesAreEqual(mono_frame, frame_);
+
+ SetFrameData(&frame_, 0);
+ AudioFrameOperations::StereoToMono(temp_frame.data_,
+ frame_.samples_per_channel_,
+ frame_.data_);
+ frame_.num_channels_ = 1; // Need to set manually.
+ VerifyFramesAreEqual(mono_frame, frame_);
+}
+
+TEST_F(AudioFrameOperationsTest, StereoToMonoDoesNotWrapAround) {
+ SetFrameData(&frame_, -32768, -32768);
+ EXPECT_EQ(0, AudioFrameOperations::StereoToMono(&frame_));
+
+ AudioFrame mono_frame;
+ mono_frame.samples_per_channel_ = 320;
+ mono_frame.num_channels_ = 1;
+ SetFrameData(&mono_frame, -32768);
+ VerifyFramesAreEqual(mono_frame, frame_);
+}
+
+TEST_F(AudioFrameOperationsTest, SwapStereoChannelsSucceedsOnStereo) {
+ SetFrameData(&frame_, 0, 1);
+
+ AudioFrame swapped_frame;
+ swapped_frame.samples_per_channel_ = 320;
+ swapped_frame.num_channels_ = 2;
+ SetFrameData(&swapped_frame, 1, 0);
+
+ AudioFrameOperations::SwapStereoChannels(&frame_);
+ VerifyFramesAreEqual(swapped_frame, frame_);
+}
+
+TEST_F(AudioFrameOperationsTest, SwapStereoChannelsFailsOnMono) {
+ frame_.num_channels_ = 1;
+ // Set data to "stereo", despite it being a mono frame.
+ SetFrameData(&frame_, 0, 1);
+
+ AudioFrame orig_frame;
+ orig_frame.CopyFrom(frame_);
+ AudioFrameOperations::SwapStereoChannels(&frame_);
+ // Verify that no swap occurred.
+ VerifyFramesAreEqual(orig_frame, frame_);
+}
+
+TEST_F(AudioFrameOperationsTest, MuteSucceeds) {
+ SetFrameData(&frame_, 1000, 1000);
+ AudioFrameOperations::Mute(frame_);
+
+ AudioFrame muted_frame;
+ muted_frame.samples_per_channel_ = 320;
+ muted_frame.num_channels_ = 2;
+ SetFrameData(&muted_frame, 0, 0);
+ VerifyFramesAreEqual(muted_frame, frame_);
+}
+
+// TODO(andrew): should not allow negative scales.
+TEST_F(AudioFrameOperationsTest, DISABLED_ScaleFailsWithBadParameters) {
+ frame_.num_channels_ = 1;
+ EXPECT_EQ(-1, AudioFrameOperations::Scale(1.0, 1.0, frame_));
+
+ frame_.num_channels_ = 3;
+ EXPECT_EQ(-1, AudioFrameOperations::Scale(1.0, 1.0, frame_));
+
+ frame_.num_channels_ = 2;
+ EXPECT_EQ(-1, AudioFrameOperations::Scale(-1.0, 1.0, frame_));
+ EXPECT_EQ(-1, AudioFrameOperations::Scale(1.0, -1.0, frame_));
+}
+
+// TODO(andrew): fix the wraparound bug. We should always saturate.
+TEST_F(AudioFrameOperationsTest, DISABLED_ScaleDoesNotWrapAround) {
+ SetFrameData(&frame_, 4000, -4000);
+ EXPECT_EQ(0, AudioFrameOperations::Scale(10.0, 10.0, frame_));
+
+ AudioFrame clipped_frame;
+ clipped_frame.samples_per_channel_ = 320;
+ clipped_frame.num_channels_ = 2;
+ SetFrameData(&clipped_frame, 32767, -32768);
+ VerifyFramesAreEqual(clipped_frame, frame_);
+}
+
+TEST_F(AudioFrameOperationsTest, ScaleSucceeds) {
+ SetFrameData(&frame_, 1, -1);
+ EXPECT_EQ(0, AudioFrameOperations::Scale(2.0, 3.0, frame_));
+
+ AudioFrame scaled_frame;
+ scaled_frame.samples_per_channel_ = 320;
+ scaled_frame.num_channels_ = 2;
+ SetFrameData(&scaled_frame, 2, -3);
+ VerifyFramesAreEqual(scaled_frame, frame_);
+}
+
+// TODO(andrew): should fail with a negative scale.
+TEST_F(AudioFrameOperationsTest, DISABLED_ScaleWithSatFailsWithBadParameters) {
+ EXPECT_EQ(-1, AudioFrameOperations::ScaleWithSat(-1.0, frame_));
+}
+
+TEST_F(AudioFrameOperationsTest, ScaleWithSatDoesNotWrapAround) {
+ frame_.num_channels_ = 1;
+ SetFrameData(&frame_, 4000);
+ EXPECT_EQ(0, AudioFrameOperations::ScaleWithSat(10.0, frame_));
+
+ AudioFrame clipped_frame;
+ clipped_frame.samples_per_channel_ = 320;
+ clipped_frame.num_channels_ = 1;
+ SetFrameData(&clipped_frame, 32767);
+ VerifyFramesAreEqual(clipped_frame, frame_);
+
+ SetFrameData(&frame_, -4000);
+ EXPECT_EQ(0, AudioFrameOperations::ScaleWithSat(10.0, frame_));
+ SetFrameData(&clipped_frame, -32768);
+ VerifyFramesAreEqual(clipped_frame, frame_);
+}
+
+TEST_F(AudioFrameOperationsTest, ScaleWithSatSucceeds) {
+ frame_.num_channels_ = 1;
+ SetFrameData(&frame_, 1);
+ EXPECT_EQ(0, AudioFrameOperations::ScaleWithSat(2.0, frame_));
+
+ AudioFrame scaled_frame;
+ scaled_frame.samples_per_channel_ = 320;
+ scaled_frame.num_channels_ = 1;
+ SetFrameData(&scaled_frame, 2);
+ VerifyFramesAreEqual(scaled_frame, frame_);
+}
+
+} // namespace
+} // namespace webrtc
diff --git a/webrtc/modules/utility/source/coder.cc b/webrtc/modules/utility/source/coder.cc
new file mode 100644
index 0000000000..4ec5f9b4e2
--- /dev/null
+++ b/webrtc/modules/utility/source/coder.cc
@@ -0,0 +1,112 @@
+/*
+ * 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/common_types.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/utility/source/coder.h"
+
+namespace webrtc {
+AudioCoder::AudioCoder(uint32_t instanceID)
+ : _acm(AudioCodingModule::Create(instanceID)),
+ _receiveCodec(),
+ _encodeTimestamp(0),
+ _encodedData(NULL),
+ _encodedLengthInBytes(0),
+ _decodeTimestamp(0)
+{
+ _acm->InitializeReceiver();
+ _acm->RegisterTransportCallback(this);
+}
+
+AudioCoder::~AudioCoder()
+{
+}
+
+int32_t AudioCoder::SetEncodeCodec(const CodecInst& codecInst)
+{
+ if(_acm->RegisterSendCodec((CodecInst&)codecInst) == -1)
+ {
+ return -1;
+ }
+ return 0;
+}
+
+int32_t AudioCoder::SetDecodeCodec(const CodecInst& codecInst)
+{
+ if(_acm->RegisterReceiveCodec((CodecInst&)codecInst) == -1)
+ {
+ return -1;
+ }
+ memcpy(&_receiveCodec,&codecInst,sizeof(CodecInst));
+ return 0;
+}
+
+int32_t AudioCoder::Decode(AudioFrame& decodedAudio,
+ uint32_t sampFreqHz,
+ const int8_t* incomingPayload,
+ size_t payloadLength)
+{
+ if (payloadLength > 0)
+ {
+ const uint8_t payloadType = _receiveCodec.pltype;
+ _decodeTimestamp += _receiveCodec.pacsize;
+ if(_acm->IncomingPayload((const uint8_t*) incomingPayload,
+ payloadLength,
+ payloadType,
+ _decodeTimestamp) == -1)
+ {
+ return -1;
+ }
+ }
+ return _acm->PlayoutData10Ms((uint16_t)sampFreqHz, &decodedAudio);
+}
+
+int32_t AudioCoder::PlayoutData(AudioFrame& decodedAudio,
+ uint16_t& sampFreqHz)
+{
+ return _acm->PlayoutData10Ms(sampFreqHz, &decodedAudio);
+}
+
+int32_t AudioCoder::Encode(const AudioFrame& audio,
+ int8_t* encodedData,
+ size_t& encodedLengthInBytes)
+{
+ // Fake a timestamp in case audio doesn't contain a correct timestamp.
+ // Make a local copy of the audio frame since audio is const
+ AudioFrame audioFrame;
+ audioFrame.CopyFrom(audio);
+ audioFrame.timestamp_ = _encodeTimestamp;
+ _encodeTimestamp += static_cast<uint32_t>(audioFrame.samples_per_channel_);
+
+ // For any codec with a frame size that is longer than 10 ms the encoded
+ // length in bytes should be zero until a a full frame has been encoded.
+ _encodedLengthInBytes = 0;
+ if(_acm->Add10MsData((AudioFrame&)audioFrame) == -1)
+ {
+ return -1;
+ }
+ _encodedData = encodedData;
+ encodedLengthInBytes = _encodedLengthInBytes;
+ return 0;
+}
+
+int32_t AudioCoder::SendData(
+ FrameType /* frameType */,
+ uint8_t /* payloadType */,
+ uint32_t /* timeStamp */,
+ const uint8_t* payloadData,
+ size_t payloadSize,
+ const RTPFragmentationHeader* /* fragmentation*/)
+{
+ memcpy(_encodedData,payloadData,sizeof(uint8_t) * payloadSize);
+ _encodedLengthInBytes = payloadSize;
+ return 0;
+}
+} // namespace webrtc
diff --git a/webrtc/modules/utility/source/coder.h b/webrtc/modules/utility/source/coder.h
new file mode 100644
index 0000000000..4270e9b380
--- /dev/null
+++ b/webrtc/modules/utility/source/coder.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_UTILITY_SOURCE_CODER_H_
+#define WEBRTC_MODULES_UTILITY_SOURCE_CODER_H_
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/common_types.h"
+#include "webrtc/modules/audio_coding/main/include/audio_coding_module.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+class AudioFrame;
+
+class AudioCoder : public AudioPacketizationCallback
+{
+public:
+ AudioCoder(uint32_t instanceID);
+ ~AudioCoder();
+
+ int32_t SetEncodeCodec(const CodecInst& codecInst);
+
+ int32_t SetDecodeCodec(const CodecInst& codecInst);
+
+ int32_t Decode(AudioFrame& decodedAudio, uint32_t sampFreqHz,
+ const int8_t* incomingPayload, size_t payloadLength);
+
+ int32_t PlayoutData(AudioFrame& decodedAudio, uint16_t& sampFreqHz);
+
+ int32_t Encode(const AudioFrame& audio, int8_t* encodedData,
+ size_t& encodedLengthInBytes);
+
+protected:
+ int32_t SendData(FrameType frameType,
+ uint8_t payloadType,
+ uint32_t timeStamp,
+ const uint8_t* payloadData,
+ size_t payloadSize,
+ const RTPFragmentationHeader* fragmentation) override;
+
+private:
+ rtc::scoped_ptr<AudioCodingModule> _acm;
+
+ CodecInst _receiveCodec;
+
+ uint32_t _encodeTimestamp;
+ int8_t* _encodedData;
+ size_t _encodedLengthInBytes;
+
+ uint32_t _decodeTimestamp;
+};
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_UTILITY_SOURCE_CODER_H_
diff --git a/webrtc/modules/utility/source/file_player_impl.cc b/webrtc/modules/utility/source/file_player_impl.cc
new file mode 100644
index 0000000000..e783a7eca8
--- /dev/null
+++ b/webrtc/modules/utility/source/file_player_impl.cc
@@ -0,0 +1,402 @@
+/*
+ * 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/utility/source/file_player_impl.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+FilePlayer* FilePlayer::CreateFilePlayer(uint32_t instanceID,
+ FileFormats fileFormat)
+{
+ switch(fileFormat)
+ {
+ case kFileFormatWavFile:
+ case kFileFormatCompressedFile:
+ case kFileFormatPreencodedFile:
+ case kFileFormatPcm16kHzFile:
+ case kFileFormatPcm8kHzFile:
+ case kFileFormatPcm32kHzFile:
+ // audio formats
+ return new FilePlayerImpl(instanceID, fileFormat);
+ default:
+ assert(false);
+ return NULL;
+ }
+}
+
+void FilePlayer::DestroyFilePlayer(FilePlayer* player)
+{
+ delete player;
+}
+
+FilePlayerImpl::FilePlayerImpl(const uint32_t instanceID,
+ const FileFormats fileFormat)
+ : _instanceID(instanceID),
+ _fileFormat(fileFormat),
+ _fileModule(*MediaFile::CreateMediaFile(instanceID)),
+ _decodedLengthInMS(0),
+ _audioDecoder(instanceID),
+ _codec(),
+ _numberOf10MsPerFrame(0),
+ _numberOf10MsInDecoder(0),
+ _resampler(),
+ _scaling(1.0)
+{
+ _codec.plfreq = 0;
+}
+
+FilePlayerImpl::~FilePlayerImpl()
+{
+ MediaFile::DestroyMediaFile(&_fileModule);
+}
+
+int32_t FilePlayerImpl::Frequency() const
+{
+ if(_codec.plfreq == 0)
+ {
+ return -1;
+ }
+ // Make sure that sample rate is 8,16 or 32 kHz. E.g. WAVE files may have
+ // other sampling rates.
+ if(_codec.plfreq == 11000)
+ {
+ return 16000;
+ }
+ else if(_codec.plfreq == 22000)
+ {
+ return 32000;
+ }
+ else if(_codec.plfreq == 44000)
+ {
+ return 32000;
+ }
+ else if(_codec.plfreq == 48000)
+ {
+ return 32000;
+ }
+ else
+ {
+ return _codec.plfreq;
+ }
+}
+
+int32_t FilePlayerImpl::AudioCodec(CodecInst& audioCodec) const
+{
+ audioCodec = _codec;
+ return 0;
+}
+
+int32_t FilePlayerImpl::Get10msAudioFromFile(
+ int16_t* outBuffer,
+ size_t& lengthInSamples,
+ int frequencyInHz)
+{
+ if(_codec.plfreq == 0)
+ {
+ LOG(LS_WARNING) << "Get10msAudioFromFile() playing not started!"
+ << " codec freq = " << _codec.plfreq
+ << ", wanted freq = " << frequencyInHz;
+ return -1;
+ }
+
+ AudioFrame unresampledAudioFrame;
+ if(STR_CASE_CMP(_codec.plname, "L16") == 0)
+ {
+ unresampledAudioFrame.sample_rate_hz_ = _codec.plfreq;
+
+ // L16 is un-encoded data. Just pull 10 ms.
+ size_t lengthInBytes =
+ sizeof(unresampledAudioFrame.data_);
+ if (_fileModule.PlayoutAudioData(
+ (int8_t*)unresampledAudioFrame.data_,
+ lengthInBytes) == -1)
+ {
+ // End of file reached.
+ return -1;
+ }
+ if(lengthInBytes == 0)
+ {
+ lengthInSamples = 0;
+ return 0;
+ }
+ // One sample is two bytes.
+ unresampledAudioFrame.samples_per_channel_ = lengthInBytes >> 1;
+
+ } else {
+ // Decode will generate 10 ms of audio data. PlayoutAudioData(..)
+ // expects a full frame. If the frame size is larger than 10 ms,
+ // PlayoutAudioData(..) data should be called proportionally less often.
+ int16_t encodedBuffer[MAX_AUDIO_BUFFER_IN_SAMPLES];
+ size_t encodedLengthInBytes = 0;
+ if(++_numberOf10MsInDecoder >= _numberOf10MsPerFrame)
+ {
+ _numberOf10MsInDecoder = 0;
+ size_t bytesFromFile = sizeof(encodedBuffer);
+ if (_fileModule.PlayoutAudioData((int8_t*)encodedBuffer,
+ bytesFromFile) == -1)
+ {
+ // End of file reached.
+ return -1;
+ }
+ encodedLengthInBytes = bytesFromFile;
+ }
+ if(_audioDecoder.Decode(unresampledAudioFrame,frequencyInHz,
+ (int8_t*)encodedBuffer,
+ encodedLengthInBytes) == -1)
+ {
+ return -1;
+ }
+ }
+
+ size_t outLen = 0;
+ if(_resampler.ResetIfNeeded(unresampledAudioFrame.sample_rate_hz_,
+ frequencyInHz, 1))
+ {
+ LOG(LS_WARNING) << "Get10msAudioFromFile() unexpected codec.";
+
+ // New sampling frequency. Update state.
+ outLen = static_cast<size_t>(frequencyInHz / 100);
+ memset(outBuffer, 0, outLen * sizeof(int16_t));
+ return 0;
+ }
+ _resampler.Push(unresampledAudioFrame.data_,
+ unresampledAudioFrame.samples_per_channel_,
+ outBuffer,
+ MAX_AUDIO_BUFFER_IN_SAMPLES,
+ outLen);
+
+ lengthInSamples = outLen;
+
+ if(_scaling != 1.0)
+ {
+ for (size_t i = 0;i < outLen; i++)
+ {
+ outBuffer[i] = (int16_t)(outBuffer[i] * _scaling);
+ }
+ }
+ _decodedLengthInMS += 10;
+ return 0;
+}
+
+int32_t FilePlayerImpl::RegisterModuleFileCallback(FileCallback* callback)
+{
+ return _fileModule.SetModuleFileCallback(callback);
+}
+
+int32_t FilePlayerImpl::SetAudioScaling(float scaleFactor)
+{
+ if((scaleFactor >= 0)&&(scaleFactor <= 2.0))
+ {
+ _scaling = scaleFactor;
+ return 0;
+ }
+ LOG(LS_WARNING) << "SetAudioScaling() non-allowed scale factor.";
+ return -1;
+}
+
+int32_t FilePlayerImpl::StartPlayingFile(const char* fileName,
+ bool loop,
+ uint32_t startPosition,
+ float volumeScaling,
+ uint32_t notification,
+ uint32_t stopPosition,
+ const CodecInst* codecInst)
+{
+ if (_fileFormat == kFileFormatPcm16kHzFile ||
+ _fileFormat == kFileFormatPcm8kHzFile||
+ _fileFormat == kFileFormatPcm32kHzFile )
+ {
+ CodecInst codecInstL16;
+ strncpy(codecInstL16.plname,"L16",32);
+ codecInstL16.pltype = 93;
+ codecInstL16.channels = 1;
+
+ if (_fileFormat == kFileFormatPcm8kHzFile)
+ {
+ codecInstL16.rate = 128000;
+ codecInstL16.plfreq = 8000;
+ codecInstL16.pacsize = 80;
+
+ } else if(_fileFormat == kFileFormatPcm16kHzFile)
+ {
+ codecInstL16.rate = 256000;
+ codecInstL16.plfreq = 16000;
+ codecInstL16.pacsize = 160;
+
+ }else if(_fileFormat == kFileFormatPcm32kHzFile)
+ {
+ codecInstL16.rate = 512000;
+ codecInstL16.plfreq = 32000;
+ codecInstL16.pacsize = 160;
+ } else
+ {
+ LOG(LS_ERROR) << "StartPlayingFile() sample frequency not "
+ << "supported for PCM format.";
+ return -1;
+ }
+
+ if (_fileModule.StartPlayingAudioFile(fileName, notification, loop,
+ _fileFormat, &codecInstL16,
+ startPosition,
+ stopPosition) == -1)
+ {
+ LOG(LS_WARNING) << "StartPlayingFile() failed to initialize "
+ << "pcm file " << fileName;
+ return -1;
+ }
+ SetAudioScaling(volumeScaling);
+ }else if(_fileFormat == kFileFormatPreencodedFile)
+ {
+ if (_fileModule.StartPlayingAudioFile(fileName, notification, loop,
+ _fileFormat, codecInst) == -1)
+ {
+ LOG(LS_WARNING) << "StartPlayingFile() failed to initialize "
+ << "pre-encoded file " << fileName;
+ return -1;
+ }
+ } else
+ {
+ CodecInst* no_inst = NULL;
+ if (_fileModule.StartPlayingAudioFile(fileName, notification, loop,
+ _fileFormat, no_inst,
+ startPosition,
+ stopPosition) == -1)
+ {
+ LOG(LS_WARNING) << "StartPlayingFile() failed to initialize file "
+ << fileName;
+ return -1;
+ }
+ SetAudioScaling(volumeScaling);
+ }
+ if (SetUpAudioDecoder() == -1)
+ {
+ StopPlayingFile();
+ return -1;
+ }
+ return 0;
+}
+
+int32_t FilePlayerImpl::StartPlayingFile(InStream& sourceStream,
+ uint32_t startPosition,
+ float volumeScaling,
+ uint32_t notification,
+ uint32_t stopPosition,
+ const CodecInst* codecInst)
+{
+ if (_fileFormat == kFileFormatPcm16kHzFile ||
+ _fileFormat == kFileFormatPcm32kHzFile ||
+ _fileFormat == kFileFormatPcm8kHzFile)
+ {
+ CodecInst codecInstL16;
+ strncpy(codecInstL16.plname,"L16",32);
+ codecInstL16.pltype = 93;
+ codecInstL16.channels = 1;
+
+ if (_fileFormat == kFileFormatPcm8kHzFile)
+ {
+ codecInstL16.rate = 128000;
+ codecInstL16.plfreq = 8000;
+ codecInstL16.pacsize = 80;
+
+ }else if (_fileFormat == kFileFormatPcm16kHzFile)
+ {
+ codecInstL16.rate = 256000;
+ codecInstL16.plfreq = 16000;
+ codecInstL16.pacsize = 160;
+
+ }else if (_fileFormat == kFileFormatPcm32kHzFile)
+ {
+ codecInstL16.rate = 512000;
+ codecInstL16.plfreq = 32000;
+ codecInstL16.pacsize = 160;
+ }else
+ {
+ LOG(LS_ERROR) << "StartPlayingFile() sample frequency not "
+ << "supported for PCM format.";
+ return -1;
+ }
+ if (_fileModule.StartPlayingAudioStream(sourceStream, notification,
+ _fileFormat, &codecInstL16,
+ startPosition,
+ stopPosition) == -1)
+ {
+ LOG(LS_ERROR) << "StartPlayingFile() failed to initialize stream "
+ << "playout.";
+ return -1;
+ }
+
+ }else if(_fileFormat == kFileFormatPreencodedFile)
+ {
+ if (_fileModule.StartPlayingAudioStream(sourceStream, notification,
+ _fileFormat, codecInst) == -1)
+ {
+ LOG(LS_ERROR) << "StartPlayingFile() failed to initialize stream "
+ << "playout.";
+ return -1;
+ }
+ } else {
+ CodecInst* no_inst = NULL;
+ if (_fileModule.StartPlayingAudioStream(sourceStream, notification,
+ _fileFormat, no_inst,
+ startPosition,
+ stopPosition) == -1)
+ {
+ LOG(LS_ERROR) << "StartPlayingFile() failed to initialize stream "
+ << "playout.";
+ return -1;
+ }
+ }
+ SetAudioScaling(volumeScaling);
+
+ if (SetUpAudioDecoder() == -1)
+ {
+ StopPlayingFile();
+ return -1;
+ }
+ return 0;
+}
+
+int32_t FilePlayerImpl::StopPlayingFile()
+{
+ memset(&_codec, 0, sizeof(CodecInst));
+ _numberOf10MsPerFrame = 0;
+ _numberOf10MsInDecoder = 0;
+ return _fileModule.StopPlaying();
+}
+
+bool FilePlayerImpl::IsPlayingFile() const
+{
+ return _fileModule.IsPlaying();
+}
+
+int32_t FilePlayerImpl::GetPlayoutPosition(uint32_t& durationMs)
+{
+ return _fileModule.PlayoutPositionMs(durationMs);
+}
+
+int32_t FilePlayerImpl::SetUpAudioDecoder()
+{
+ if ((_fileModule.codec_info(_codec) == -1))
+ {
+ LOG(LS_WARNING) << "Failed to retrieve codec info of file data.";
+ return -1;
+ }
+ if( STR_CASE_CMP(_codec.plname, "L16") != 0 &&
+ _audioDecoder.SetDecodeCodec(_codec) == -1)
+ {
+ LOG(LS_WARNING) << "SetUpAudioDecoder() codec " << _codec.plname
+ << " not supported.";
+ return -1;
+ }
+ _numberOf10MsPerFrame = _codec.pacsize / (_codec.plfreq / 100);
+ _numberOf10MsInDecoder = 0;
+ return 0;
+}
+} // namespace webrtc
diff --git a/webrtc/modules/utility/source/file_player_impl.h b/webrtc/modules/utility/source/file_player_impl.h
new file mode 100644
index 0000000000..f411db9151
--- /dev/null
+++ b/webrtc/modules/utility/source/file_player_impl.h
@@ -0,0 +1,79 @@
+/*
+ * 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_UTILITY_SOURCE_FILE_PLAYER_IMPL_H_
+#define WEBRTC_MODULES_UTILITY_SOURCE_FILE_PLAYER_IMPL_H_
+
+#include "webrtc/common_audio/resampler/include/resampler.h"
+#include "webrtc/common_types.h"
+#include "webrtc/engine_configurations.h"
+#include "webrtc/modules/media_file/interface/media_file.h"
+#include "webrtc/modules/media_file/interface/media_file_defines.h"
+#include "webrtc/modules/utility/interface/file_player.h"
+#include "webrtc/modules/utility/source/coder.h"
+#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+class FilePlayerImpl : public FilePlayer
+{
+public:
+ FilePlayerImpl(uint32_t instanceID, FileFormats fileFormat);
+ ~FilePlayerImpl();
+
+ virtual int Get10msAudioFromFile(
+ int16_t* outBuffer,
+ size_t& lengthInSamples,
+ int frequencyInHz);
+ virtual int32_t RegisterModuleFileCallback(FileCallback* callback);
+ virtual int32_t StartPlayingFile(
+ const char* fileName,
+ bool loop,
+ uint32_t startPosition,
+ float volumeScaling,
+ uint32_t notification,
+ uint32_t stopPosition = 0,
+ const CodecInst* codecInst = NULL);
+ virtual int32_t StartPlayingFile(
+ InStream& sourceStream,
+ uint32_t startPosition,
+ float volumeScaling,
+ uint32_t notification,
+ uint32_t stopPosition = 0,
+ const CodecInst* codecInst = NULL);
+ virtual int32_t StopPlayingFile();
+ virtual bool IsPlayingFile() const;
+ virtual int32_t GetPlayoutPosition(uint32_t& durationMs);
+ virtual int32_t AudioCodec(CodecInst& audioCodec) const;
+ virtual int32_t Frequency() const;
+ virtual int32_t SetAudioScaling(float scaleFactor);
+
+protected:
+ int32_t SetUpAudioDecoder();
+
+ uint32_t _instanceID;
+ const FileFormats _fileFormat;
+ MediaFile& _fileModule;
+
+ uint32_t _decodedLengthInMS;
+
+private:
+ AudioCoder _audioDecoder;
+
+ CodecInst _codec;
+ int32_t _numberOf10MsPerFrame;
+ int32_t _numberOf10MsInDecoder;
+
+ Resampler _resampler;
+ float _scaling;
+};
+} // namespace webrtc
+#endif // WEBRTC_MODULES_UTILITY_SOURCE_FILE_PLAYER_IMPL_H_
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 0000000000..4b65acdeef
--- /dev/null
+++ b/webrtc/modules/utility/source/file_player_unittests.cc
@@ -0,0 +1,107 @@
+/*
+ * 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 <stdio.h>
+#include <string>
+
+#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"
+#include "webrtc/test/testsupport/gtest_disable.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) {}
+
+ 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);
+ }
+ }
+
+ 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};
+ size_t 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(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, DISABLED_ON_IOS(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, DISABLED_ON_IOS(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
new file mode 100644
index 0000000000..13926deb4a
--- /dev/null
+++ b/webrtc/modules/utility/source/file_recorder_impl.cc
@@ -0,0 +1,261 @@
+/*
+ * 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/common_video/libyuv/include/webrtc_libyuv.h"
+#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/include/logging.h"
+
+namespace webrtc {
+FileRecorder* FileRecorder::CreateFileRecorder(uint32_t instanceID,
+ FileFormats fileFormat)
+{
+ return new FileRecorderImpl(instanceID, fileFormat);
+}
+
+void FileRecorder::DestroyFileRecorder(FileRecorder* recorder)
+{
+ delete recorder;
+}
+
+FileRecorderImpl::FileRecorderImpl(uint32_t instanceID,
+ FileFormats fileFormat)
+ : _instanceID(instanceID),
+ _fileFormat(fileFormat),
+ _moduleFile(MediaFile::CreateMediaFile(_instanceID)),
+ codec_info_(),
+ _audioBuffer(),
+ _audioEncoder(instanceID),
+ _audioResampler()
+{
+}
+
+FileRecorderImpl::~FileRecorderImpl()
+{
+ MediaFile::DestroyMediaFile(_moduleFile);
+}
+
+FileFormats FileRecorderImpl::RecordingFileFormat() const
+{
+ return _fileFormat;
+}
+
+int32_t FileRecorderImpl::RegisterModuleFileCallback(
+ FileCallback* callback)
+{
+ if(_moduleFile == NULL)
+ {
+ return -1;
+ }
+ return _moduleFile->SetModuleFileCallback(callback);
+}
+
+int32_t FileRecorderImpl::StartRecordingAudioFile(
+ const char* fileName,
+ const CodecInst& codecInst,
+ uint32_t notificationTimeMs)
+{
+ if(_moduleFile == NULL)
+ {
+ return -1;
+ }
+ codec_info_ = codecInst;
+ int32_t retVal = 0;
+ retVal =_moduleFile->StartRecordingAudioFile(fileName, _fileFormat,
+ codecInst,
+ notificationTimeMs);
+
+ if( retVal == 0)
+ {
+ retVal = SetUpAudioEncoder();
+ }
+ if( retVal != 0)
+ {
+ LOG(LS_WARNING) << "Failed to initialize file " << fileName
+ << " for recording.";
+
+ if(IsRecording())
+ {
+ StopRecording();
+ }
+ }
+ return retVal;
+}
+
+int32_t FileRecorderImpl::StartRecordingAudioFile(
+ OutStream& destStream,
+ const CodecInst& codecInst,
+ uint32_t notificationTimeMs)
+{
+ codec_info_ = codecInst;
+ int32_t retVal = _moduleFile->StartRecordingAudioStream(
+ destStream,
+ _fileFormat,
+ codecInst,
+ notificationTimeMs);
+
+ if( retVal == 0)
+ {
+ retVal = SetUpAudioEncoder();
+ }
+ if( retVal != 0)
+ {
+ LOG(LS_WARNING) << "Failed to initialize outStream for recording.";
+
+ if(IsRecording())
+ {
+ StopRecording();
+ }
+ }
+ return retVal;
+}
+
+int32_t FileRecorderImpl::StopRecording()
+{
+ memset(&codec_info_, 0, sizeof(CodecInst));
+ return _moduleFile->StopRecording();
+}
+
+bool FileRecorderImpl::IsRecording() const
+{
+ return _moduleFile->IsRecording();
+}
+
+int32_t FileRecorderImpl::RecordAudioToFile(
+ const AudioFrame& incomingAudioFrame,
+ const TickTime* playoutTS)
+{
+ if (codec_info_.plfreq == 0)
+ {
+ LOG(LS_WARNING) << "RecordAudioToFile() recording audio is not "
+ << "turned on.";
+ return -1;
+ }
+ AudioFrame tempAudioFrame;
+ tempAudioFrame.samples_per_channel_ = 0;
+ if( incomingAudioFrame.num_channels_ == 2 &&
+ !_moduleFile->IsStereo())
+ {
+ // Recording mono but incoming audio is (interleaved) stereo.
+ tempAudioFrame.num_channels_ = 1;
+ tempAudioFrame.sample_rate_hz_ = incomingAudioFrame.sample_rate_hz_;
+ tempAudioFrame.samples_per_channel_ =
+ incomingAudioFrame.samples_per_channel_;
+ for (size_t i = 0;
+ i < (incomingAudioFrame.samples_per_channel_); i++)
+ {
+ // Sample value is the average of left and right buffer rounded to
+ // closest integer value. Note samples can be either 1 or 2 byte.
+ tempAudioFrame.data_[i] =
+ ((incomingAudioFrame.data_[2 * i] +
+ incomingAudioFrame.data_[(2 * i) + 1] + 1) >> 1);
+ }
+ }
+ else if( incomingAudioFrame.num_channels_ == 1 &&
+ _moduleFile->IsStereo())
+ {
+ // Recording stereo but incoming audio is mono.
+ tempAudioFrame.num_channels_ = 2;
+ tempAudioFrame.sample_rate_hz_ = incomingAudioFrame.sample_rate_hz_;
+ tempAudioFrame.samples_per_channel_ =
+ incomingAudioFrame.samples_per_channel_;
+ for (size_t i = 0;
+ i < (incomingAudioFrame.samples_per_channel_); i++)
+ {
+ // Duplicate sample to both channels
+ tempAudioFrame.data_[2*i] =
+ incomingAudioFrame.data_[i];
+ tempAudioFrame.data_[2*i+1] =
+ incomingAudioFrame.data_[i];
+ }
+ }
+
+ const AudioFrame* ptrAudioFrame = &incomingAudioFrame;
+ if(tempAudioFrame.samples_per_channel_ != 0)
+ {
+ // If ptrAudioFrame is not empty it contains the audio to be recorded.
+ ptrAudioFrame = &tempAudioFrame;
+ }
+
+ // Encode the audio data before writing to file. Don't encode if the codec
+ // is PCM.
+ // NOTE: stereo recording is only supported for WAV files.
+ // TODO (hellner): WAV expect PCM in little endian byte order. Not
+ // "encoding" with PCM coder should be a problem for big endian systems.
+ size_t encodedLenInBytes = 0;
+ if (_fileFormat == kFileFormatPreencodedFile ||
+ STR_CASE_CMP(codec_info_.plname, "L16") != 0)
+ {
+ if (_audioEncoder.Encode(*ptrAudioFrame, _audioBuffer,
+ encodedLenInBytes) == -1)
+ {
+ LOG(LS_WARNING) << "RecordAudioToFile() codec "
+ << codec_info_.plname
+ << " not supported or failed to encode stream.";
+ return -1;
+ }
+ } else {
+ size_t outLen = 0;
+ _audioResampler.ResetIfNeeded(ptrAudioFrame->sample_rate_hz_,
+ codec_info_.plfreq,
+ ptrAudioFrame->num_channels_);
+ _audioResampler.Push(ptrAudioFrame->data_,
+ ptrAudioFrame->samples_per_channel_ *
+ ptrAudioFrame->num_channels_,
+ (int16_t*)_audioBuffer,
+ MAX_AUDIO_BUFFER_IN_BYTES, outLen);
+ encodedLenInBytes = outLen * sizeof(int16_t);
+ }
+
+ // Codec may not be operating at a frame rate of 10 ms. Whenever enough
+ // 10 ms chunks of data has been pushed to the encoder an encoded frame
+ // will be available. Wait until then.
+ if (encodedLenInBytes)
+ {
+ if (WriteEncodedAudioData(_audioBuffer, encodedLenInBytes) == -1)
+ {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int32_t FileRecorderImpl::SetUpAudioEncoder()
+{
+ if (_fileFormat == kFileFormatPreencodedFile ||
+ STR_CASE_CMP(codec_info_.plname, "L16") != 0)
+ {
+ if(_audioEncoder.SetEncodeCodec(codec_info_) == -1)
+ {
+ LOG(LS_ERROR) << "SetUpAudioEncoder() codec "
+ << codec_info_.plname << " not supported.";
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int32_t FileRecorderImpl::codec_info(CodecInst& codecInst) const
+{
+ if(codec_info_.plfreq == 0)
+ {
+ return -1;
+ }
+ codecInst = codec_info_;
+ return 0;
+}
+
+int32_t FileRecorderImpl::WriteEncodedAudioData(const int8_t* audioBuffer,
+ size_t bufferLength)
+{
+ return _moduleFile->IncomingAudioData(audioBuffer, bufferLength);
+}
+} // namespace webrtc
diff --git a/webrtc/modules/utility/source/file_recorder_impl.h b/webrtc/modules/utility/source/file_recorder_impl.h
new file mode 100644
index 0000000000..8ea96bdad4
--- /dev/null
+++ b/webrtc/modules/utility/source/file_recorder_impl.h
@@ -0,0 +1,93 @@
+/*
+ * 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 a class that can write audio and/or video to file in
+// multiple file formats. The unencoded input data is written to file in the
+// encoded format specified.
+
+#ifndef WEBRTC_MODULES_UTILITY_SOURCE_FILE_RECORDER_IMPL_H_
+#define WEBRTC_MODULES_UTILITY_SOURCE_FILE_RECORDER_IMPL_H_
+
+#include <list>
+
+#include "webrtc/common_audio/resampler/include/resampler.h"
+#include "webrtc/common_types.h"
+#include "webrtc/engine_configurations.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/media_file/interface/media_file.h"
+#include "webrtc/modules/media_file/interface/media_file_defines.h"
+#include "webrtc/modules/utility/interface/file_recorder.h"
+#include "webrtc/modules/utility/source/coder.h"
+#include "webrtc/system_wrappers/include/event_wrapper.h"
+#include "webrtc/system_wrappers/include/thread_wrapper.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+// The largest decoded frame size in samples (60ms with 32kHz sample rate).
+enum { MAX_AUDIO_BUFFER_IN_SAMPLES = 60*32};
+enum { MAX_AUDIO_BUFFER_IN_BYTES = MAX_AUDIO_BUFFER_IN_SAMPLES*2};
+enum { kMaxAudioBufferQueueLength = 100 };
+
+class CriticalSectionWrapper;
+
+class FileRecorderImpl : public FileRecorder
+{
+public:
+ FileRecorderImpl(uint32_t instanceID, FileFormats fileFormat);
+ virtual ~FileRecorderImpl();
+
+ // FileRecorder functions.
+ virtual int32_t RegisterModuleFileCallback(FileCallback* callback);
+ virtual FileFormats RecordingFileFormat() const;
+ virtual int32_t StartRecordingAudioFile(
+ const char* fileName,
+ const CodecInst& codecInst,
+ uint32_t notificationTimeMs) override;
+ virtual int32_t StartRecordingAudioFile(
+ OutStream& destStream,
+ const CodecInst& codecInst,
+ uint32_t notificationTimeMs) override;
+ virtual int32_t StopRecording();
+ virtual bool IsRecording() const;
+ virtual int32_t codec_info(CodecInst& codecInst) const;
+ virtual int32_t RecordAudioToFile(
+ const AudioFrame& frame,
+ const TickTime* playoutTS = NULL);
+ virtual int32_t StartRecordingVideoFile(
+ const char* fileName,
+ const CodecInst& audioCodecInst,
+ const VideoCodec& videoCodecInst,
+ bool videoOnly = false) override
+ {
+ return -1;
+ }
+ virtual int32_t RecordVideoToFile(const VideoFrame& videoFrame) {
+ return -1;
+ }
+
+protected:
+ int32_t WriteEncodedAudioData(const int8_t* audioBuffer,
+ size_t bufferLength);
+
+ int32_t SetUpAudioEncoder();
+
+ uint32_t _instanceID;
+ FileFormats _fileFormat;
+ MediaFile* _moduleFile;
+
+private:
+ CodecInst codec_info_;
+ int8_t _audioBuffer[MAX_AUDIO_BUFFER_IN_BYTES];
+ AudioCoder _audioEncoder;
+ Resampler _audioResampler;
+};
+} // namespace webrtc
+#endif // WEBRTC_MODULES_UTILITY_SOURCE_FILE_RECORDER_IMPL_H_
diff --git a/webrtc/modules/utility/source/helpers_android.cc b/webrtc/modules/utility/source/helpers_android.cc
new file mode 100644
index 0000000000..25652f237e
--- /dev/null
+++ b/webrtc/modules/utility/source/helpers_android.cc
@@ -0,0 +1,123 @@
+/*
+ * 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/base/checks.h"
+#include "webrtc/modules/utility/interface/helpers_android.h"
+
+#include <android/log.h>
+#include <assert.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <unistd.h>
+
+#define TAG "HelpersAndroid"
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
+
+namespace webrtc {
+
+JNIEnv* GetEnv(JavaVM* jvm) {
+ void* env = NULL;
+ jint status = jvm->GetEnv(&env, JNI_VERSION_1_6);
+ RTC_CHECK(((env != NULL) && (status == JNI_OK)) ||
+ ((env == NULL) && (status == JNI_EDETACHED)))
+ << "Unexpected GetEnv return: " << status << ":" << env;
+ return reinterpret_cast<JNIEnv*>(env);
+}
+
+// Return a |jlong| that will correctly convert back to |ptr|. This is needed
+// because the alternative (of silently passing a 32-bit pointer to a vararg
+// function expecting a 64-bit param) picks up garbage in the high 32 bits.
+jlong PointerTojlong(void* ptr) {
+ static_assert(sizeof(intptr_t) <= sizeof(jlong),
+ "Time to rethink the use of jlongs");
+ // Going through intptr_t to be obvious about the definedness of the
+ // conversion from pointer to integral type. intptr_t to jlong is a standard
+ // widening by the static_assert above.
+ jlong ret = reinterpret_cast<intptr_t>(ptr);
+ RTC_DCHECK(reinterpret_cast<void*>(ret) == ptr);
+ return ret;
+}
+
+jmethodID GetMethodID (
+ JNIEnv* jni, jclass c, const char* name, const char* signature) {
+ jmethodID m = jni->GetMethodID(c, name, signature);
+ CHECK_EXCEPTION(jni) << "Error during GetMethodID: " << name << ", "
+ << signature;
+ RTC_CHECK(m) << name << ", " << signature;
+ return m;
+}
+
+jmethodID GetStaticMethodID (
+ JNIEnv* jni, jclass c, const char* name, const char* signature) {
+ jmethodID m = jni->GetStaticMethodID(c, name, signature);
+ CHECK_EXCEPTION(jni) << "Error during GetStaticMethodID: " << name << ", "
+ << signature;
+ RTC_CHECK(m) << name << ", " << signature;
+ return m;
+}
+
+jclass FindClass(JNIEnv* jni, const char* name) {
+ jclass c = jni->FindClass(name);
+ CHECK_EXCEPTION(jni) << "Error during FindClass: " << name;
+ RTC_CHECK(c) << name;
+ return c;
+}
+
+jobject NewGlobalRef(JNIEnv* jni, jobject o) {
+ jobject ret = jni->NewGlobalRef(o);
+ CHECK_EXCEPTION(jni) << "Error during NewGlobalRef";
+ RTC_CHECK(ret);
+ return ret;
+}
+
+void DeleteGlobalRef(JNIEnv* jni, jobject o) {
+ jni->DeleteGlobalRef(o);
+ CHECK_EXCEPTION(jni) << "Error during DeleteGlobalRef";
+}
+
+std::string GetThreadId() {
+ char buf[21]; // Big enough to hold a kuint64max plus terminating NULL.
+ int thread_id = gettid();
+ RTC_CHECK_LT(snprintf(buf, sizeof(buf), "%i", thread_id),
+ static_cast<int>(sizeof(buf)))
+ << "Thread id is bigger than uint64??";
+ return std::string(buf);
+}
+
+std::string GetThreadInfo() {
+ return "@[tid=" + GetThreadId() + "]";
+}
+
+AttachThreadScoped::AttachThreadScoped(JavaVM* jvm)
+ : attached_(false), jvm_(jvm), env_(NULL) {
+ env_ = GetEnv(jvm);
+ if (!env_) {
+ // Adding debug log here so we can track down potential leaks and figure
+ // out why we sometimes see "Native thread exiting without having called
+ // DetachCurrentThread" in logcat outputs.
+ ALOGD("Attaching thread to JVM%s", GetThreadInfo().c_str());
+ jint res = jvm->AttachCurrentThread(&env_, NULL);
+ attached_ = (res == JNI_OK);
+ RTC_CHECK(attached_) << "AttachCurrentThread failed: " << res;
+ }
+}
+
+AttachThreadScoped::~AttachThreadScoped() {
+ if (attached_) {
+ ALOGD("Detaching thread from JVM%s", GetThreadInfo().c_str());
+ jint res = jvm_->DetachCurrentThread();
+ RTC_CHECK(res == JNI_OK) << "DetachCurrentThread failed: " << res;
+ RTC_CHECK(!GetEnv(jvm_));
+ }
+}
+
+JNIEnv* AttachThreadScoped::env() { return env_; }
+
+} // namespace webrtc
diff --git a/webrtc/modules/utility/source/helpers_ios.mm b/webrtc/modules/utility/source/helpers_ios.mm
new file mode 100644
index 0000000000..90b7c8f605
--- /dev/null
+++ b/webrtc/modules/utility/source/helpers_ios.mm
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can 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_IOS)
+
+#import <AVFoundation/AVFoundation.h>
+#import <Foundation/Foundation.h>
+#import <sys/sysctl.h>
+#import <UIKit/UIKit.h>
+
+#include "webrtc/base/checks.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/utility/interface/helpers_ios.h"
+
+namespace webrtc {
+namespace ios {
+
+// TODO(henrika): move to shared location.
+// See https://code.google.com/p/webrtc/issues/detail?id=4773 for details.
+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<const char*>([charData bytes]),
+ [charData length]);
+}
+
+bool CheckAndLogError(BOOL success, NSError* error) {
+ if (!success) {
+ NSString* msg =
+ [NSString stringWithFormat:@"Error: %ld, %@, %@", (long)error.code,
+ error.localizedDescription,
+ error.localizedFailureReason];
+ LOG(LS_ERROR) << StdStringFromNSString(msg);
+ return false;
+ }
+ return true;
+}
+
+// TODO(henrika): see if it is possible to move to GetThreadName in
+// platform_thread.h and base it on pthread methods instead.
+std::string GetCurrentThreadDescription() {
+ NSString* name = [NSString stringWithFormat:@"%@", [NSThread currentThread]];
+ return StdStringFromNSString(name);
+}
+
+std::string GetAudioSessionCategory() {
+ NSString* category = [[AVAudioSession sharedInstance] category];
+ return StdStringFromNSString(category);
+}
+
+std::string GetSystemName() {
+ NSString* osName = [[UIDevice currentDevice] systemName];
+ return StdStringFromNSString(osName);
+}
+
+std::string GetSystemVersion() {
+ NSString* osVersion = [[UIDevice currentDevice] systemVersion];
+ return StdStringFromNSString(osVersion);
+}
+
+float GetSystemVersionAsFloat() {
+ NSString* osVersion = [[UIDevice currentDevice] systemVersion];
+ return osVersion.floatValue;
+}
+
+std::string GetDeviceType() {
+ NSString* deviceModel = [[UIDevice currentDevice] model];
+ return StdStringFromNSString(deviceModel);
+}
+
+std::string GetDeviceName() {
+ size_t size;
+ sysctlbyname("hw.machine", NULL, &size, NULL, 0);
+ rtc::scoped_ptr<char[]> machine;
+ machine.reset(new char[size]);
+ sysctlbyname("hw.machine", machine.get(), &size, NULL, 0);
+ std::string raw_name(machine.get());
+ if (!raw_name.compare("iPhone1,1"))
+ return std::string("iPhone 1G");
+ if (!raw_name.compare("iPhone1,2"))
+ return std::string("iPhone 3G");
+ if (!raw_name.compare("iPhone2,1"))
+ return std::string("iPhone 3GS");
+ if (!raw_name.compare("iPhone3,1"))
+ return std::string("iPhone 4");
+ if (!raw_name.compare("iPhone3,3"))
+ return std::string("Verizon iPhone 4");
+ if (!raw_name.compare("iPhone4,1"))
+ return std::string("iPhone 4S");
+ if (!raw_name.compare("iPhone5,1"))
+ return std::string("iPhone 5 (GSM)");
+ if (!raw_name.compare("iPhone5,2"))
+ return std::string("iPhone 5 (GSM+CDMA)");
+ if (!raw_name.compare("iPhone5,3"))
+ return std::string("iPhone 5c (GSM)");
+ if (!raw_name.compare("iPhone5,4"))
+ return std::string("iPhone 5c (GSM+CDMA)");
+ if (!raw_name.compare("iPhone6,1"))
+ return std::string("iPhone 5s (GSM)");
+ if (!raw_name.compare("iPhone6,2"))
+ return std::string("iPhone 5s (GSM+CDMA)");
+ if (!raw_name.compare("iPhone7,1"))
+ return std::string("iPhone 6 Plus");
+ if (!raw_name.compare("iPhone7,2"))
+ return std::string("iPhone 6");
+ if (!raw_name.compare("iPhone8,1"))
+ return std::string("iPhone 6s");
+ if (!raw_name.compare("iPhone8,2"))
+ return std::string("iPhone 6s Plus");
+ if (!raw_name.compare("iPod1,1"))
+ return std::string("iPod Touch 1G");
+ if (!raw_name.compare("iPod2,1"))
+ return std::string("iPod Touch 2G");
+ if (!raw_name.compare("iPod3,1"))
+ return std::string("iPod Touch 3G");
+ if (!raw_name.compare("iPod4,1"))
+ return std::string("iPod Touch 4G");
+ if (!raw_name.compare("iPod5,1"))
+ return std::string("iPod Touch 5G");
+ if (!raw_name.compare("iPad1,1"))
+ return std::string("iPad");
+ if (!raw_name.compare("iPad2,1"))
+ return std::string("iPad 2 (WiFi)");
+ if (!raw_name.compare("iPad2,2"))
+ return std::string("iPad 2 (GSM)");
+ if (!raw_name.compare("iPad2,3"))
+ return std::string("iPad 2 (CDMA)");
+ if (!raw_name.compare("iPad2,4"))
+ return std::string("iPad 2 (WiFi)");
+ if (!raw_name.compare("iPad2,5"))
+ return std::string("iPad Mini (WiFi)");
+ if (!raw_name.compare("iPad2,6"))
+ return std::string("iPad Mini (GSM)");
+ if (!raw_name.compare("iPad2,7"))
+ return std::string("iPad Mini (GSM+CDMA)");
+ if (!raw_name.compare("iPad3,1"))
+ return std::string("iPad 3 (WiFi)");
+ if (!raw_name.compare("iPad3,2"))
+ return std::string("iPad 3 (GSM+CDMA)");
+ if (!raw_name.compare("iPad3,3"))
+ return std::string("iPad 3 (GSM)");
+ if (!raw_name.compare("iPad3,4"))
+ return std::string("iPad 4 (WiFi)");
+ if (!raw_name.compare("iPad3,5"))
+ return std::string("iPad 4 (GSM)");
+ if (!raw_name.compare("iPad3,6"))
+ return std::string("iPad 4 (GSM+CDMA)");
+ if (!raw_name.compare("iPad4,1"))
+ return std::string("iPad Air (WiFi)");
+ if (!raw_name.compare("iPad4,2"))
+ return std::string("iPad Air (Cellular)");
+ if (!raw_name.compare("iPad4,4"))
+ return std::string("iPad mini 2G (WiFi)");
+ if (!raw_name.compare("iPad4,5"))
+ return std::string("iPad mini 2G (Cellular)");
+ if (!raw_name.compare("i386"))
+ return std::string("Simulator");
+ if (!raw_name.compare("x86_64"))
+ return std::string("Simulator");
+ LOG(LS_WARNING) << "Failed to find device name (" << raw_name << ")";
+ return raw_name;
+}
+
+} // namespace ios
+} // namespace webrtc
+
+#endif // defined(WEBRTC_IOS)
diff --git a/webrtc/modules/utility/source/jvm_android.cc b/webrtc/modules/utility/source/jvm_android.cc
new file mode 100644
index 0000000000..648c1685ea
--- /dev/null
+++ b/webrtc/modules/utility/source/jvm_android.cc
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can 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 <android/log.h>
+
+#include "webrtc/modules/utility/interface/jvm_android.h"
+
+#include "webrtc/base/checks.h"
+
+#define TAG "JVM"
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
+
+namespace webrtc {
+
+JVM* g_jvm;
+
+// TODO(henrika): add more clases here if needed.
+struct {
+ const char* name;
+ jclass clazz;
+} loaded_classes[] = {
+ {"org/webrtc/voiceengine/BuildInfo", nullptr},
+ {"org/webrtc/voiceengine/WebRtcAudioManager", nullptr},
+ {"org/webrtc/voiceengine/WebRtcAudioRecord", nullptr},
+ {"org/webrtc/voiceengine/WebRtcAudioTrack", nullptr},
+};
+
+// Android's FindClass() is trickier than usual because the app-specific
+// ClassLoader is not consulted when there is no app-specific frame on the
+// stack. Consequently, we only look up all classes once in native WebRTC.
+// http://developer.android.com/training/articles/perf-jni.html#faq_FindClass
+void LoadClasses(JNIEnv* jni) {
+ for (auto& c : loaded_classes) {
+ jclass localRef = FindClass(jni, c.name);
+ CHECK_EXCEPTION(jni) << "Error during FindClass: " << c.name;
+ RTC_CHECK(localRef) << c.name;
+ jclass globalRef = reinterpret_cast<jclass>(jni->NewGlobalRef(localRef));
+ CHECK_EXCEPTION(jni) << "Error during NewGlobalRef: " << c.name;
+ RTC_CHECK(globalRef) << c.name;
+ c.clazz = globalRef;
+ }
+}
+
+void FreeClassReferences(JNIEnv* jni) {
+ for (auto& c : loaded_classes) {
+ jni->DeleteGlobalRef(c.clazz);
+ c.clazz = nullptr;
+ }
+}
+
+jclass LookUpClass(const char* name) {
+ for (auto& c : loaded_classes) {
+ if (strcmp(c.name, name) == 0)
+ return c.clazz;
+ }
+ RTC_CHECK(false) << "Unable to find class in lookup table";
+ return 0;
+}
+
+// AttachCurrentThreadIfNeeded implementation.
+AttachCurrentThreadIfNeeded::AttachCurrentThreadIfNeeded()
+ : attached_(false) {
+ ALOGD("AttachCurrentThreadIfNeeded::ctor%s", GetThreadInfo().c_str());
+ JavaVM* jvm = JVM::GetInstance()->jvm();
+ RTC_CHECK(jvm);
+ JNIEnv* jni = GetEnv(jvm);
+ if (!jni) {
+ ALOGD("Attaching thread to JVM");
+ JNIEnv* env = nullptr;
+ jint ret = jvm->AttachCurrentThread(&env, nullptr);
+ attached_ = (ret == JNI_OK);
+ }
+}
+
+AttachCurrentThreadIfNeeded::~AttachCurrentThreadIfNeeded() {
+ ALOGD("AttachCurrentThreadIfNeeded::dtor%s", GetThreadInfo().c_str());
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ if (attached_) {
+ ALOGD("Detaching thread from JVM");
+ jint res = JVM::GetInstance()->jvm()->DetachCurrentThread();
+ RTC_CHECK(res == JNI_OK) << "DetachCurrentThread failed: " << res;
+ }
+}
+
+// GlobalRef implementation.
+GlobalRef::GlobalRef(JNIEnv* jni, jobject object)
+ : jni_(jni), j_object_(NewGlobalRef(jni, object)) {
+ ALOGD("GlobalRef::ctor%s", GetThreadInfo().c_str());
+}
+
+GlobalRef::~GlobalRef() {
+ ALOGD("GlobalRef::dtor%s", GetThreadInfo().c_str());
+ DeleteGlobalRef(jni_, j_object_);
+}
+
+jboolean GlobalRef::CallBooleanMethod(jmethodID methodID, ...) {
+ va_list args;
+ va_start(args, methodID);
+ jboolean res = jni_->CallBooleanMethodV(j_object_, methodID, args);
+ CHECK_EXCEPTION(jni_) << "Error during CallBooleanMethod";
+ va_end(args);
+ return res;
+}
+
+jint GlobalRef::CallIntMethod(jmethodID methodID, ...) {
+ va_list args;
+ va_start(args, methodID);
+ jint res = jni_->CallIntMethodV(j_object_, methodID, args);
+ CHECK_EXCEPTION(jni_) << "Error during CallIntMethod";
+ va_end(args);
+ return res;
+}
+
+void GlobalRef::CallVoidMethod(jmethodID methodID, ...) {
+ va_list args;
+ va_start(args, methodID);
+ jni_->CallVoidMethodV(j_object_, methodID, args);
+ CHECK_EXCEPTION(jni_) << "Error during CallVoidMethod";
+ va_end(args);
+}
+
+// NativeRegistration implementation.
+NativeRegistration::NativeRegistration(JNIEnv* jni, jclass clazz)
+ : JavaClass(jni, clazz), jni_(jni) {
+ ALOGD("NativeRegistration::ctor%s", GetThreadInfo().c_str());
+}
+
+NativeRegistration::~NativeRegistration() {
+ ALOGD("NativeRegistration::dtor%s", GetThreadInfo().c_str());
+ jni_->UnregisterNatives(j_class_);
+ CHECK_EXCEPTION(jni_) << "Error during UnregisterNatives";
+}
+
+rtc::scoped_ptr<GlobalRef> NativeRegistration::NewObject(
+ const char* name, const char* signature, ...) {
+ ALOGD("NativeRegistration::NewObject%s", GetThreadInfo().c_str());
+ va_list args;
+ va_start(args, signature);
+ jobject obj = jni_->NewObjectV(j_class_,
+ GetMethodID(jni_, j_class_, name, signature),
+ args);
+ CHECK_EXCEPTION(jni_) << "Error during NewObjectV";
+ va_end(args);
+ return rtc::scoped_ptr<GlobalRef>(new GlobalRef(jni_, obj));
+}
+
+// JavaClass implementation.
+jmethodID JavaClass::GetMethodId(
+ const char* name, const char* signature) {
+ return GetMethodID(jni_, j_class_, name, signature);
+}
+
+jmethodID JavaClass::GetStaticMethodId(
+ const char* name, const char* signature) {
+ return GetStaticMethodID(jni_, j_class_, name, signature);
+}
+
+jobject JavaClass::CallStaticObjectMethod(jmethodID methodID, ...) {
+ va_list args;
+ va_start(args, methodID);
+ jobject res = jni_->CallStaticObjectMethod(j_class_, methodID, args);
+ CHECK_EXCEPTION(jni_) << "Error during CallStaticObjectMethod";
+ return res;
+}
+
+// JNIEnvironment implementation.
+JNIEnvironment::JNIEnvironment(JNIEnv* jni) : jni_(jni) {
+ ALOGD("JNIEnvironment::ctor%s", GetThreadInfo().c_str());
+}
+
+JNIEnvironment::~JNIEnvironment() {
+ ALOGD("JNIEnvironment::dtor%s", GetThreadInfo().c_str());
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+rtc::scoped_ptr<NativeRegistration> JNIEnvironment::RegisterNatives(
+ const char* name, const JNINativeMethod *methods, int num_methods) {
+ ALOGD("JNIEnvironment::RegisterNatives(%s)", name);
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ jclass clazz = LookUpClass(name);
+ jni_->RegisterNatives(clazz, methods, num_methods);
+ CHECK_EXCEPTION(jni_) << "Error during RegisterNatives";
+ return rtc::scoped_ptr<NativeRegistration>(
+ new NativeRegistration(jni_, clazz));
+}
+
+std::string JNIEnvironment::JavaToStdString(const jstring& j_string) {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ const char* jchars = jni_->GetStringUTFChars(j_string, nullptr);
+ CHECK_EXCEPTION(jni_);
+ const int size = jni_->GetStringUTFLength(j_string);
+ CHECK_EXCEPTION(jni_);
+ std::string ret(jchars, size);
+ jni_->ReleaseStringUTFChars(j_string, jchars);
+ CHECK_EXCEPTION(jni_);
+ return ret;
+}
+
+// static
+void JVM::Initialize(JavaVM* jvm, jobject context) {
+ ALOGD("JVM::Initialize%s", GetThreadInfo().c_str());
+ RTC_CHECK(!g_jvm);
+ g_jvm = new JVM(jvm, context);
+}
+
+// static
+void JVM::Uninitialize() {
+ ALOGD("JVM::Uninitialize%s", GetThreadInfo().c_str());
+ RTC_DCHECK(g_jvm);
+ delete g_jvm;
+ g_jvm = nullptr;
+}
+
+// static
+JVM* JVM::GetInstance() {
+ RTC_DCHECK(g_jvm);
+ return g_jvm;
+}
+
+JVM::JVM(JavaVM* jvm, jobject context)
+ : jvm_(jvm) {
+ ALOGD("JVM::JVM%s", GetThreadInfo().c_str());
+ RTC_CHECK(jni()) << "AttachCurrentThread() must be called on this thread.";
+ context_ = NewGlobalRef(jni(), context);
+ LoadClasses(jni());
+}
+
+JVM::~JVM() {
+ ALOGD("JVM::~JVM%s", GetThreadInfo().c_str());
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ FreeClassReferences(jni());
+ DeleteGlobalRef(jni(), context_);
+}
+
+rtc::scoped_ptr<JNIEnvironment> JVM::environment() {
+ ALOGD("JVM::environment%s", GetThreadInfo().c_str());
+ // The JNIEnv is used for thread-local storage. For this reason, we cannot
+ // share a JNIEnv between threads. If a piece of code has no other way to get
+ // its JNIEnv, we should share the JavaVM, and use GetEnv to discover the
+ // thread's JNIEnv. (Assuming it has one, if not, use AttachCurrentThread).
+ // See // http://developer.android.com/training/articles/perf-jni.html.
+ JNIEnv* jni = GetEnv(jvm_);
+ if (!jni) {
+ ALOGE("AttachCurrentThread() has not been called on this thread.");
+ return rtc::scoped_ptr<JNIEnvironment>();
+ }
+ return rtc::scoped_ptr<JNIEnvironment>(new JNIEnvironment(jni));
+}
+
+JavaClass JVM::GetClass(const char* name) {
+ ALOGD("JVM::GetClass(%s)%s", name, GetThreadInfo().c_str());
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ return JavaClass(jni(), LookUpClass(name));
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/utility/source/process_thread_impl.cc b/webrtc/modules/utility/source/process_thread_impl.cc
new file mode 100644
index 0000000000..04fa88739f
--- /dev/null
+++ b/webrtc/modules/utility/source/process_thread_impl.cc
@@ -0,0 +1,237 @@
+/*
+ * 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/utility/source/process_thread_impl.h"
+
+#include "webrtc/base/checks.h"
+#include "webrtc/modules/interface/module.h"
+#include "webrtc/system_wrappers/include/logging.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+
+namespace webrtc {
+namespace {
+
+// We use this constant internally to signal that a module has requested
+// a callback right away. When this is set, no call to TimeUntilNextProcess
+// should be made, but Process() should be called directly.
+const int64_t kCallProcessImmediately = -1;
+
+int64_t GetNextCallbackTime(Module* module, int64_t time_now) {
+ int64_t interval = module->TimeUntilNextProcess();
+ if (interval < 0) {
+ // Falling behind, we should call the callback now.
+ return time_now;
+ }
+ return time_now + interval;
+}
+}
+
+ProcessThread::~ProcessThread() {}
+
+// static
+rtc::scoped_ptr<ProcessThread> ProcessThread::Create(
+ const char* thread_name) {
+ return rtc::scoped_ptr<ProcessThread>(new ProcessThreadImpl(thread_name))
+ .Pass();
+}
+
+ProcessThreadImpl::ProcessThreadImpl(const char* thread_name)
+ : wake_up_(EventWrapper::Create()),
+ stop_(false),
+ thread_name_(thread_name) {}
+
+ProcessThreadImpl::~ProcessThreadImpl() {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ RTC_DCHECK(!thread_.get());
+ RTC_DCHECK(!stop_);
+
+ while (!queue_.empty()) {
+ delete queue_.front();
+ queue_.pop();
+ }
+}
+
+void ProcessThreadImpl::Start() {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ RTC_DCHECK(!thread_.get());
+ if (thread_.get())
+ return;
+
+ RTC_DCHECK(!stop_);
+
+ {
+ // TODO(tommi): Since DeRegisterModule is currently being called from
+ // different threads in some cases (ChannelOwner), we need to lock access to
+ // the modules_ collection even on the controller thread.
+ // Once we've cleaned up those places, we can remove this lock.
+ rtc::CritScope lock(&lock_);
+ for (ModuleCallback& m : modules_)
+ m.module->ProcessThreadAttached(this);
+ }
+
+ thread_ = ThreadWrapper::CreateThread(&ProcessThreadImpl::Run, this,
+ thread_name_);
+ RTC_CHECK(thread_->Start());
+}
+
+void ProcessThreadImpl::Stop() {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ if(!thread_.get())
+ return;
+
+ {
+ rtc::CritScope lock(&lock_);
+ stop_ = true;
+ }
+
+ wake_up_->Set();
+
+ RTC_CHECK(thread_->Stop());
+ stop_ = false;
+
+ // TODO(tommi): Since DeRegisterModule is currently being called from
+ // different threads in some cases (ChannelOwner), we need to lock access to
+ // the modules_ collection even on the controller thread.
+ // Since DeRegisterModule also checks thread_, we also need to hold the
+ // lock for the .reset() operation.
+ // Once we've cleaned up those places, we can remove this lock.
+ rtc::CritScope lock(&lock_);
+ thread_.reset();
+ for (ModuleCallback& m : modules_)
+ m.module->ProcessThreadAttached(nullptr);
+}
+
+void ProcessThreadImpl::WakeUp(Module* module) {
+ // Allowed to be called on any thread.
+ {
+ rtc::CritScope lock(&lock_);
+ for (ModuleCallback& m : modules_) {
+ if (m.module == module)
+ m.next_callback = kCallProcessImmediately;
+ }
+ }
+ wake_up_->Set();
+}
+
+void ProcessThreadImpl::PostTask(rtc::scoped_ptr<ProcessTask> task) {
+ // Allowed to be called on any thread.
+ {
+ rtc::CritScope lock(&lock_);
+ queue_.push(task.release());
+ }
+ wake_up_->Set();
+}
+
+void ProcessThreadImpl::RegisterModule(Module* module) {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ RTC_DCHECK(module);
+
+#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON))
+ {
+ // Catch programmer error.
+ rtc::CritScope lock(&lock_);
+ for (const ModuleCallback& mc : modules_)
+ RTC_DCHECK(mc.module != module);
+ }
+#endif
+
+ // Now that we know the module isn't in the list, we'll call out to notify
+ // the module that it's attached to the worker thread. We don't hold
+ // the lock while we make this call.
+ if (thread_.get())
+ module->ProcessThreadAttached(this);
+
+ {
+ rtc::CritScope lock(&lock_);
+ modules_.push_back(ModuleCallback(module));
+ }
+
+ // 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.
+ wake_up_->Set();
+}
+
+void ProcessThreadImpl::DeRegisterModule(Module* module) {
+ // Allowed to be called on any thread.
+ // TODO(tommi): Disallow this ^^^
+ RTC_DCHECK(module);
+
+ {
+ rtc::CritScope lock(&lock_);
+ modules_.remove_if([&module](const ModuleCallback& m) {
+ return m.module == module;
+ });
+
+ // TODO(tommi): we currently need to hold the lock while calling out to
+ // ProcessThreadAttached. This is to make sure that the thread hasn't been
+ // destroyed while we attach the module. Once we can make sure
+ // DeRegisterModule isn't being called on arbitrary threads, we can move the
+ // |if (thread_.get())| check and ProcessThreadAttached() call outside the
+ // lock scope.
+
+ // Notify the module that it's been detached.
+ if (thread_.get())
+ module->ProcessThreadAttached(nullptr);
+ }
+}
+
+// static
+bool ProcessThreadImpl::Run(void* obj) {
+ return static_cast<ProcessThreadImpl*>(obj)->Process();
+}
+
+bool ProcessThreadImpl::Process() {
+ int64_t now = TickTime::MillisecondTimestamp();
+ int64_t next_checkpoint = now + (1000 * 60);
+
+ {
+ rtc::CritScope lock(&lock_);
+ if (stop_)
+ return false;
+ for (ModuleCallback& m : modules_) {
+ // TODO(tommi): Would be good to measure the time TimeUntilNextProcess
+ // takes and dcheck if it takes too long (e.g. >=10ms). Ideally this
+ // operation should not require taking a lock, so querying all modules
+ // should run in a matter of nanoseconds.
+ if (m.next_callback == 0)
+ m.next_callback = GetNextCallbackTime(m.module, now);
+
+ if (m.next_callback <= now ||
+ m.next_callback == kCallProcessImmediately) {
+ m.module->Process();
+ // Use a new 'now' reference to calculate when the next callback
+ // should occur. We'll continue to use 'now' above for the baseline
+ // of calculating how long we should wait, to reduce variance.
+ int64_t new_now = TickTime::MillisecondTimestamp();
+ m.next_callback = GetNextCallbackTime(m.module, new_now);
+ }
+
+ if (m.next_callback < next_checkpoint)
+ next_checkpoint = m.next_callback;
+ }
+
+ while (!queue_.empty()) {
+ ProcessTask* task = queue_.front();
+ queue_.pop();
+ lock_.Leave();
+ task->Run();
+ delete task;
+ lock_.Enter();
+ }
+ }
+
+ int64_t time_to_wait = next_checkpoint - TickTime::MillisecondTimestamp();
+ if (time_to_wait > 0)
+ wake_up_->Wait(static_cast<unsigned long>(time_to_wait));
+
+ return true;
+}
+} // namespace webrtc
diff --git a/webrtc/modules/utility/source/process_thread_impl.h b/webrtc/modules/utility/source/process_thread_impl.h
new file mode 100644
index 0000000000..4e5861b41e
--- /dev/null
+++ b/webrtc/modules/utility/source/process_thread_impl.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_UTILITY_SOURCE_PROCESS_THREAD_IMPL_H_
+#define WEBRTC_MODULES_UTILITY_SOURCE_PROCESS_THREAD_IMPL_H_
+
+#include <list>
+#include <queue>
+
+#include "webrtc/base/criticalsection.h"
+#include "webrtc/base/thread_checker.h"
+#include "webrtc/modules/utility/interface/process_thread.h"
+#include "webrtc/system_wrappers/include/event_wrapper.h"
+#include "webrtc/system_wrappers/include/thread_wrapper.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+class ProcessThreadImpl : public ProcessThread {
+ public:
+ explicit ProcessThreadImpl(const char* thread_name);
+ ~ProcessThreadImpl() override;
+
+ void Start() override;
+ void Stop() override;
+
+ void WakeUp(Module* module) override;
+ void PostTask(rtc::scoped_ptr<ProcessTask> task) override;
+
+ void RegisterModule(Module* module) override;
+ void DeRegisterModule(Module* module) override;
+
+ protected:
+ static bool Run(void* obj);
+ bool Process();
+
+ private:
+ struct ModuleCallback {
+ ModuleCallback() : module(nullptr), next_callback(0) {}
+ ModuleCallback(const ModuleCallback& cb)
+ : module(cb.module), next_callback(cb.next_callback) {}
+ ModuleCallback(Module* module) : module(module), next_callback(0) {}
+ bool operator==(const ModuleCallback& cb) const {
+ return cb.module == module;
+ }
+
+ Module* const module;
+ int64_t next_callback; // Absolute timestamp.
+
+ private:
+ ModuleCallback& operator=(ModuleCallback&);
+ };
+
+ typedef std::list<ModuleCallback> ModuleList;
+
+ // Warning: For some reason, if |lock_| comes immediately before |modules_|
+ // with the current class layout, we will start to have mysterious crashes
+ // on Mac 10.9 debug. I (Tommi) suspect we're hitting some obscure alignemnt
+ // issues, but I haven't figured out what they are, if there are alignment
+ // requirements for mutexes on Mac or if there's something else to it.
+ // So be careful with changing the layout.
+ rtc::CriticalSection lock_; // Used to guard modules_, tasks_ and stop_.
+
+ rtc::ThreadChecker thread_checker_;
+ const rtc::scoped_ptr<EventWrapper> wake_up_;
+ rtc::scoped_ptr<ThreadWrapper> thread_;
+
+ ModuleList modules_;
+ // TODO(tommi): Support delayed tasks.
+ std::queue<ProcessTask*> queue_;
+ bool stop_;
+ const char* thread_name_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_UTILITY_SOURCE_PROCESS_THREAD_IMPL_H_
diff --git a/webrtc/modules/utility/source/process_thread_impl_unittest.cc b/webrtc/modules/utility/source/process_thread_impl_unittest.cc
new file mode 100644
index 0000000000..e080545312
--- /dev/null
+++ b/webrtc/modules/utility/source/process_thread_impl_unittest.cc
@@ -0,0 +1,304 @@
+/*
+ * 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 "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/interface/module.h"
+#include "webrtc/modules/utility/source/process_thread_impl.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+
+namespace webrtc {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+
+class MockModule : public Module {
+ public:
+ MOCK_METHOD0(TimeUntilNextProcess, int64_t());
+ MOCK_METHOD0(Process, int32_t());
+ MOCK_METHOD1(ProcessThreadAttached, void(ProcessThread*));
+};
+
+class RaiseEventTask : public ProcessTask {
+ public:
+ RaiseEventTask(EventWrapper* event) : event_(event) {}
+ void Run() override { event_->Set(); }
+
+ private:
+ EventWrapper* event_;
+};
+
+ACTION_P(SetEvent, event) {
+ event->Set();
+}
+
+ACTION_P(Increment, counter) {
+ ++(*counter);
+}
+
+ACTION_P(SetTimestamp, ptr) {
+ *ptr = TickTime::MillisecondTimestamp();
+}
+
+TEST(ProcessThreadImpl, StartStop) {
+ ProcessThreadImpl thread("ProcessThread");
+ thread.Start();
+ thread.Stop();
+}
+
+TEST(ProcessThreadImpl, MultipleStartStop) {
+ ProcessThreadImpl thread("ProcessThread");
+ for (int i = 0; i < 5; ++i) {
+ thread.Start();
+ thread.Stop();
+ }
+}
+
+// Verifies that we get at least call back to Process() on the worker thread.
+TEST(ProcessThreadImpl, ProcessCall) {
+ ProcessThreadImpl thread("ProcessThread");
+ thread.Start();
+
+ rtc::scoped_ptr<EventWrapper> event(EventWrapper::Create());
+
+ MockModule module;
+ EXPECT_CALL(module, TimeUntilNextProcess()).WillRepeatedly(Return(0));
+ EXPECT_CALL(module, Process())
+ .WillOnce(DoAll(SetEvent(event.get()), Return(0)))
+ .WillRepeatedly(Return(0));
+ EXPECT_CALL(module, ProcessThreadAttached(&thread)).Times(1);
+
+ thread.RegisterModule(&module);
+ EXPECT_EQ(kEventSignaled, event->Wait(100));
+
+ EXPECT_CALL(module, ProcessThreadAttached(nullptr)).Times(1);
+ thread.Stop();
+}
+
+// Same as ProcessCall except the module is registered before the
+// call to Start().
+TEST(ProcessThreadImpl, ProcessCall2) {
+ ProcessThreadImpl thread("ProcessThread");
+ rtc::scoped_ptr<EventWrapper> event(EventWrapper::Create());
+
+ MockModule module;
+ EXPECT_CALL(module, TimeUntilNextProcess()).WillRepeatedly(Return(0));
+ EXPECT_CALL(module, Process())
+ .WillOnce(DoAll(SetEvent(event.get()), Return(0)))
+ .WillRepeatedly(Return(0));
+
+ thread.RegisterModule(&module);
+
+ EXPECT_CALL(module, ProcessThreadAttached(&thread)).Times(1);
+ thread.Start();
+ EXPECT_EQ(kEventSignaled, event->Wait(100));
+
+ EXPECT_CALL(module, ProcessThreadAttached(nullptr)).Times(1);
+ thread.Stop();
+}
+
+// Tests setting up a module for callbacks and then unregister that module.
+// After unregistration, we should not receive any further callbacks.
+TEST(ProcessThreadImpl, Deregister) {
+ ProcessThreadImpl thread("ProcessThread");
+ rtc::scoped_ptr<EventWrapper> event(EventWrapper::Create());
+
+ int process_count = 0;
+ MockModule module;
+ EXPECT_CALL(module, TimeUntilNextProcess()).WillRepeatedly(Return(0));
+ EXPECT_CALL(module, Process())
+ .WillOnce(DoAll(SetEvent(event.get()),
+ Increment(&process_count),
+ Return(0)))
+ .WillRepeatedly(DoAll(Increment(&process_count), Return(0)));
+
+ thread.RegisterModule(&module);
+
+ EXPECT_CALL(module, ProcessThreadAttached(&thread)).Times(1);
+ thread.Start();
+
+ EXPECT_EQ(kEventSignaled, event->Wait(100));
+
+ EXPECT_CALL(module, ProcessThreadAttached(nullptr)).Times(1);
+ thread.DeRegisterModule(&module);
+
+ EXPECT_GE(process_count, 1);
+ int count_after_deregister = process_count;
+
+ // We shouldn't get any more callbacks.
+ EXPECT_EQ(kEventTimeout, event->Wait(20));
+ EXPECT_EQ(count_after_deregister, process_count);
+ thread.Stop();
+}
+
+// Helper function for testing receiving a callback after a certain amount of
+// time. There's some variance of timing built into it to reduce chance of
+// flakiness on bots.
+void ProcessCallAfterAFewMs(int64_t milliseconds) {
+ ProcessThreadImpl thread("ProcessThread");
+ thread.Start();
+
+ rtc::scoped_ptr<EventWrapper> event(EventWrapper::Create());
+
+ MockModule module;
+ int64_t start_time = 0;
+ int64_t called_time = 0;
+ EXPECT_CALL(module, TimeUntilNextProcess())
+ .WillOnce(DoAll(SetTimestamp(&start_time),
+ Return(milliseconds)))
+ .WillRepeatedly(Return(milliseconds));
+ EXPECT_CALL(module, Process())
+ .WillOnce(DoAll(SetTimestamp(&called_time),
+ SetEvent(event.get()),
+ Return(0)))
+ .WillRepeatedly(Return(0));
+
+ EXPECT_CALL(module, ProcessThreadAttached(&thread)).Times(1);
+ thread.RegisterModule(&module);
+
+ // Add a buffer of 50ms due to slowness of some trybots
+ // (e.g. win_drmemory_light)
+ EXPECT_EQ(kEventSignaled, event->Wait(milliseconds + 50));
+
+ EXPECT_CALL(module, ProcessThreadAttached(nullptr)).Times(1);
+ thread.Stop();
+
+ ASSERT_GT(start_time, 0);
+ ASSERT_GT(called_time, 0);
+ // Use >= instead of > since due to rounding and timer accuracy (or lack
+ // thereof), can make the test run in "0"ms time.
+ EXPECT_GE(called_time, start_time);
+ // Check for an acceptable range.
+ uint32_t diff = called_time - start_time;
+ EXPECT_GE(diff, milliseconds - 15);
+ EXPECT_LT(diff, milliseconds + 15);
+}
+
+// DISABLED for now since the virtual build bots are too slow :(
+// TODO(tommi): Fix.
+TEST(ProcessThreadImpl, DISABLED_ProcessCallAfter5ms) {
+ ProcessCallAfterAFewMs(5);
+}
+
+// DISABLED for now since the virtual build bots are too slow :(
+// TODO(tommi): Fix.
+TEST(ProcessThreadImpl, DISABLED_ProcessCallAfter50ms) {
+ ProcessCallAfterAFewMs(50);
+}
+
+// DISABLED for now since the virtual build bots are too slow :(
+// TODO(tommi): Fix.
+TEST(ProcessThreadImpl, DISABLED_ProcessCallAfter200ms) {
+ ProcessCallAfterAFewMs(200);
+}
+
+// Runs callbacks with the goal of getting up to 50 callbacks within a second
+// (on average 1 callback every 20ms). On real hardware, we're usually pretty
+// close to that, but the test bots that run on virtual machines, will
+// typically be in the range 30-40 callbacks.
+// DISABLED for now since this can take up to 2 seconds to run on the slowest
+// build bots.
+// TODO(tommi): Fix.
+TEST(ProcessThreadImpl, DISABLED_Process50Times) {
+ ProcessThreadImpl thread("ProcessThread");
+ thread.Start();
+
+ rtc::scoped_ptr<EventWrapper> event(EventWrapper::Create());
+
+ MockModule module;
+ int callback_count = 0;
+ // Ask for a callback after 20ms.
+ EXPECT_CALL(module, TimeUntilNextProcess())
+ .WillRepeatedly(Return(20));
+ EXPECT_CALL(module, Process())
+ .WillRepeatedly(DoAll(Increment(&callback_count),
+ Return(0)));
+
+ EXPECT_CALL(module, ProcessThreadAttached(&thread)).Times(1);
+ thread.RegisterModule(&module);
+
+ EXPECT_EQ(kEventTimeout, event->Wait(1000));
+
+ EXPECT_CALL(module, ProcessThreadAttached(nullptr)).Times(1);
+ thread.Stop();
+
+ printf("Callback count: %i\n", callback_count);
+ // Check that we got called back up to 50 times.
+ // Some of the try bots run on slow virtual machines, so the lower bound
+ // is much more relaxed to avoid flakiness.
+ EXPECT_GE(callback_count, 25);
+ EXPECT_LE(callback_count, 50);
+}
+
+// Tests that we can wake up the worker thread to give us a callback right
+// away when we know the thread is sleeping.
+TEST(ProcessThreadImpl, WakeUp) {
+ ProcessThreadImpl thread("ProcessThread");
+ thread.Start();
+
+ rtc::scoped_ptr<EventWrapper> started(EventWrapper::Create());
+ rtc::scoped_ptr<EventWrapper> called(EventWrapper::Create());
+
+ MockModule module;
+ int64_t start_time = 0;
+ int64_t called_time = 0;
+ // Ask for a callback after 1000ms.
+ // TimeUntilNextProcess will be called twice.
+ // The first time we use it to get the thread into a waiting state.
+ // Then we wake the thread and there should not be another call made to
+ // TimeUntilNextProcess before Process() is called.
+ // The second time TimeUntilNextProcess is then called, is after Process
+ // has been called and we don't expect any more calls.
+ EXPECT_CALL(module, TimeUntilNextProcess())
+ .WillOnce(DoAll(SetTimestamp(&start_time),
+ SetEvent(started.get()),
+ Return(1000)))
+ .WillOnce(Return(1000));
+ EXPECT_CALL(module, Process())
+ .WillOnce(DoAll(SetTimestamp(&called_time),
+ SetEvent(called.get()),
+ Return(0)))
+ .WillRepeatedly(Return(0));
+
+ EXPECT_CALL(module, ProcessThreadAttached(&thread)).Times(1);
+ thread.RegisterModule(&module);
+
+ EXPECT_EQ(kEventSignaled, started->Wait(100));
+ thread.WakeUp(&module);
+ EXPECT_EQ(kEventSignaled, called->Wait(100));
+
+ EXPECT_CALL(module, ProcessThreadAttached(nullptr)).Times(1);
+ thread.Stop();
+
+ ASSERT_GT(start_time, 0);
+ ASSERT_GT(called_time, 0);
+ EXPECT_GE(called_time, start_time);
+ uint32_t diff = called_time - start_time;
+ // We should have been called back much quicker than 1sec.
+ EXPECT_LE(diff, 100u);
+}
+
+// Tests that we can post a task that gets run straight away on the worker
+// thread.
+TEST(ProcessThreadImpl, PostTask) {
+ ProcessThreadImpl thread("ProcessThread");
+ rtc::scoped_ptr<EventWrapper> task_ran(EventWrapper::Create());
+ rtc::scoped_ptr<RaiseEventTask> task(new RaiseEventTask(task_ran.get()));
+ thread.Start();
+ thread.PostTask(task.Pass());
+ EXPECT_EQ(kEventSignaled, task_ran->Wait(100));
+ thread.Stop();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/utility/utility.gypi b/webrtc/modules/utility/utility.gypi
new file mode 100644
index 0000000000..38c9e3ebd9
--- /dev/null
+++ b/webrtc/modules/utility/utility.gypi
@@ -0,0 +1,43 @@
+# 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'webrtc_utility',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'audio_coding_module',
+ 'media_file',
+ '<(webrtc_root)/common_audio/common_audio.gyp:common_audio',
+ '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers',
+ ],
+ 'sources': [
+ 'interface/audio_frame_operations.h',
+ 'interface/file_player.h',
+ 'interface/file_recorder.h',
+ 'interface/helpers_android.h',
+ 'interface/helpers_ios.h',
+ 'interface/jvm_android.h',
+ 'interface/process_thread.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/helpers_ios.mm',
+ 'source/jvm_android.cc',
+ 'source/process_thread_impl.cc',
+ 'source/process_thread_impl.h',
+ ],
+ },
+ ], # targets
+}