diff options
Diffstat (limited to 'webrtc/modules/utility')
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 +} |