From cd617502a6b111892d59dd6e0759dbb11db30fac Mon Sep 17 00:00:00 2001 From: Dave Hylands Date: Tue, 6 Nov 2012 22:34:33 -0800 Subject: [PATCH 01/77] Bug 794599 - Add a preference to disable logging. r=bzbarsky --- b2g/chrome/content/settings.js | 7 ++++++- xpcom/base/nsConsoleService.cpp | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/b2g/chrome/content/settings.js b/b2g/chrome/content/settings.js index 9c730ef2274..ea7141e76be 100644 --- a/b2g/chrome/content/settings.js +++ b/b2g/chrome/content/settings.js @@ -92,6 +92,12 @@ for each (let [setting, defaultValue, streamType] in audioSettings) { })(setting, defaultValue, streamType); } +// =================== Console ====================== + +SettingsListener.observe('debug.console.enabled', true, function(value) { + Services.prefs.setBoolPref('consoleservice.enabled', value); +}); + // =================== Languages ==================== SettingsListener.observe('language.current', 'en-US', function(value) { Services.prefs.setCharPref('general.useragent.locale', value); @@ -114,7 +120,6 @@ SettingsListener.observe('language.current', 'en-US', function(value) { shell.start(); }); - // =================== RIL ==================== (function RILSettingsToPrefs() { let strPrefs = ['ril.data.mmsc', 'ril.data.mmsproxy']; diff --git a/xpcom/base/nsConsoleService.cpp b/xpcom/base/nsConsoleService.cpp index 137cb9ef96e..2909f3b0584 100644 --- a/xpcom/base/nsConsoleService.cpp +++ b/xpcom/base/nsConsoleService.cpp @@ -19,6 +19,8 @@ #include "nsConsoleMessage.h" #include "nsIClassInfoImpl.h" +#include "mozilla/Preferences.h" + #if defined(ANDROID) #include #endif @@ -34,6 +36,8 @@ NS_IMPL_CLASSINFO(nsConsoleService, NULL, nsIClassInfo::THREADSAFE | nsIClassInf NS_IMPL_QUERY_INTERFACE1_CI(nsConsoleService, nsIConsoleService) NS_IMPL_CI_INTERFACE_GETTER1(nsConsoleService, nsIConsoleService) +static bool sLoggingEnabled = true; + nsConsoleService::nsConsoleService() : mMessages(nullptr) , mCurrent(0) @@ -71,6 +75,7 @@ nsConsoleService::Init() memset(mMessages, 0, mBufferSize * sizeof(nsIConsoleMessage *)); mListeners.Init(); + Preferences::AddBoolVarCache(&sLoggingEnabled, "consoleservice.enabled", true); return NS_OK; } @@ -130,6 +135,10 @@ nsConsoleService::LogMessage(nsIConsoleMessage *message) if (message == nullptr) return NS_ERROR_INVALID_ARG; + if (!sLoggingEnabled) { + return NS_OK; + } + if (NS_IsMainThread() && mDeliveringMessage) { NS_WARNING("Some console listener threw an error while inside itself. Discarding this message"); return NS_ERROR_FAILURE; @@ -203,7 +212,11 @@ nsConsoleService::LogMessage(nsIConsoleMessage *message) NS_IMETHODIMP nsConsoleService::LogStringMessage(const PRUnichar *message) { - nsConsoleMessage *msg = new nsConsoleMessage(message); + if (!sLoggingEnabled) { + return NS_OK; + } + + nsRefPtr msg(new nsConsoleMessage(message)); return this->LogMessage(msg); } From 7ce4642106e4a1f9c83ce8c205b42663743dac24 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Wed, 7 Nov 2012 19:56:54 +1300 Subject: [PATCH 02/77] Bug 808469 - Make gfxSharedImageSurface take a template parameter for the base surface type. r=cjones --HG-- rename : gfx/thebes/gfxSharedImageSurface.cpp => gfx/thebes/gfxBaseSharedMemorySurface.cpp rename : gfx/thebes/gfxSharedImageSurface.h => gfx/thebes/gfxBaseSharedMemorySurface.h --- gfx/thebes/Makefile.in | 3 +- gfx/thebes/gfxBaseSharedMemorySurface.cpp | 10 ++ gfx/thebes/gfxBaseSharedMemorySurface.h | 172 ++++++++++++++++++++++ gfx/thebes/gfxImageSurface.h | 2 +- gfx/thebes/gfxSharedImageSurface.cpp | 95 ------------ gfx/thebes/gfxSharedImageSurface.h | 106 ++----------- 6 files changed, 195 insertions(+), 193 deletions(-) create mode 100644 gfx/thebes/gfxBaseSharedMemorySurface.cpp create mode 100644 gfx/thebes/gfxBaseSharedMemorySurface.h delete mode 100644 gfx/thebes/gfxSharedImageSurface.cpp diff --git a/gfx/thebes/Makefile.in b/gfx/thebes/Makefile.in index 0438a6e4052..f49c74d4836 100644 --- a/gfx/thebes/Makefile.in +++ b/gfx/thebes/Makefile.in @@ -49,6 +49,7 @@ EXPORTS = \ gfxUtils.h \ gfxUserFontSet.h \ nsSurfaceTexture.h \ + gfxBaseSharedMemorySurface.h \ gfxSharedImageSurface.h \ gfxReusableSurfaceWrapper.h \ gfxSVGGlyphs.h \ @@ -179,7 +180,7 @@ CPPSRCS = \ gfxUtils.cpp \ gfxScriptItemizer.cpp \ gfxHarfBuzzShaper.cpp \ - gfxSharedImageSurface.cpp \ + gfxBaseSharedMemorySurface.cpp \ gfxReusableSurfaceWrapper.cpp \ nsSurfaceTexture.cpp \ gfxSVGGlyphs.cpp \ diff --git a/gfx/thebes/gfxBaseSharedMemorySurface.cpp b/gfx/thebes/gfxBaseSharedMemorySurface.cpp new file mode 100644 index 00000000000..2bb1ad394eb --- /dev/null +++ b/gfx/thebes/gfxBaseSharedMemorySurface.cpp @@ -0,0 +1,10 @@ +// vim:set ts=4 sts=4 sw=4 et cin: +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxSharedImageSurface.h" + +const cairo_user_data_key_t SHM_KEY = {0}; + diff --git a/gfx/thebes/gfxBaseSharedMemorySurface.h b/gfx/thebes/gfxBaseSharedMemorySurface.h new file mode 100644 index 00000000000..c6b4b23016f --- /dev/null +++ b/gfx/thebes/gfxBaseSharedMemorySurface.h @@ -0,0 +1,172 @@ +// vim:set ts=4 sts=4 sw=4 et cin: +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SHARED_MEMORYSURFACE_H +#define GFX_SHARED_MEMORYSURFACE_H + +#include "mozilla/ipc/Shmem.h" +#include "mozilla/ipc/SharedMemory.h" + +#include "gfxASurface.h" +#include "gfxImageSurface.h" +#include "cairo.h" + +struct SharedImageInfo { + int32_t width; + int32_t height; + int32_t format; +}; + +inline SharedImageInfo* +GetShmInfoPtr(const mozilla::ipc::Shmem& aShmem) +{ + return reinterpret_cast + (aShmem.get() + aShmem.Size() - sizeof(SharedImageInfo)); +} + +extern const cairo_user_data_key_t SHM_KEY; + +template +class THEBES_API gfxBaseSharedMemorySurface : public Base { + typedef mozilla::ipc::SharedMemory SharedMemory; + typedef mozilla::ipc::Shmem Shmem; + +public: + virtual ~gfxBaseSharedMemorySurface() + { + MOZ_COUNT_DTOR(gfxBaseSharedMemorySurface); + } + + /** + * Return a new gfxSharedImageSurface around a shmem segment newly + * allocated by this function. |aAllocator| is the object used to + * allocate the new shmem segment. Null is returned if creating + * the surface failed. + * + * NB: the *caller* is responsible for freeing the Shmem allocated + * by this function. + */ + template + static already_AddRefed + Create(ShmemAllocator* aAllocator, + const gfxIntSize& aSize, + gfxASurface::gfxImageFormat aFormat, + SharedMemory::SharedMemoryType aShmType = SharedMemory::TYPE_BASIC) + { + return Create(aAllocator, aSize, aFormat, aShmType); + } + + /** + * Return a new gfxSharedImageSurface that wraps a shmem segment + * already created by the Create() above. Bad things will happen + * if an attempt is made to wrap any other shmem segment. Null is + * returned if creating the surface failed. + */ + static already_AddRefed + Open(const Shmem& aShmem) + { + SharedImageInfo* shmInfo = GetShmInfoPtr(aShmem); + gfxIntSize size(shmInfo->width, shmInfo->height); + if (!gfxASurface::CheckSurfaceSize(size)) + return nullptr; + + gfxASurface::gfxImageFormat format = (gfxASurface::gfxImageFormat)shmInfo->format; + long stride = gfxImageSurface::ComputeStride(size, format); + + nsRefPtr s = + new Sub(size, + stride, + format, + aShmem); + // We didn't create this Shmem and so don't free it on errors + return (s->CairoStatus() != 0) ? nullptr : s.forget(); + } + + template + static already_AddRefed + CreateUnsafe(ShmemAllocator* aAllocator, + const gfxIntSize& aSize, + gfxASurface::gfxImageFormat aFormat, + SharedMemory::SharedMemoryType aShmType = SharedMemory::TYPE_BASIC) + { + return Create(aAllocator, aSize, aFormat, aShmType); + } + + Shmem& GetShmem() { return mShmem; } + + static bool IsSharedImage(gfxASurface *aSurface) + { + return (aSurface + && aSurface->GetType() == gfxASurface::SurfaceTypeImage + && aSurface->GetData(&SHM_KEY)); + } + +protected: + gfxBaseSharedMemorySurface(const gfxIntSize& aSize, long aStride, + gfxASurface::gfxImageFormat aFormat, + const Shmem& aShmem) + : Base(aShmem.get(), aSize, aStride, aFormat) + { + MOZ_COUNT_CTOR(gfxBaseSharedMemorySurface); + + mShmem = aShmem; + this->SetData(&SHM_KEY, this, nullptr); + } + +private: + void WriteShmemInfo() + { + SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem); + shmInfo->width = this->mSize.width; + shmInfo->height = this->mSize.height; + shmInfo->format = this->mFormat; + } + + static size_t GetAlignedSize(const gfxIntSize& aSize, long aStride) + { + #define MOZ_ALIGN_WORD(x) (((x) + 3) & ~3) + return MOZ_ALIGN_WORD(sizeof(SharedImageInfo) + aSize.height * aStride); + } + + template + static already_AddRefed + Create(ShmemAllocator* aAllocator, + const gfxIntSize& aSize, + gfxASurface::gfxImageFormat aFormat, + SharedMemory::SharedMemoryType aShmType) + { + if (!gfxASurface::CheckSurfaceSize(aSize)) + return nullptr; + + Shmem shmem; + long stride = gfxImageSurface::ComputeStride(aSize, aFormat); + size_t size = GetAlignedSize(aSize, stride); + if (!Unsafe) { + if (!aAllocator->AllocShmem(size, aShmType, &shmem)) + return nullptr; + } else { + if (!aAllocator->AllocUnsafeShmem(size, aShmType, &shmem)) + return nullptr; + } + + nsRefPtr s = + new Sub(aSize, stride, aFormat, shmem); + if (s->CairoStatus() != 0) { + aAllocator->DeallocShmem(shmem); + return nullptr; + } + s->WriteShmemInfo(); + return s.forget(); + } + + Shmem mShmem; + + // Calling these is very bad, disallow it + gfxBaseSharedMemorySurface(const gfxBaseSharedMemorySurface&); + gfxBaseSharedMemorySurface& operator=(const gfxBaseSharedMemorySurface&); +}; + +#endif /* GFX_SHARED_MEMORYSURFACE_H */ diff --git a/gfx/thebes/gfxImageSurface.h b/gfx/thebes/gfxImageSurface.h index a7ad813510f..23f68ebb363 100644 --- a/gfx/thebes/gfxImageSurface.h +++ b/gfx/thebes/gfxImageSurface.h @@ -92,6 +92,7 @@ public: virtual void MovePixels(const nsIntRect& aSourceRect, const nsIntPoint& aDestTopLeft) MOZ_OVERRIDE; + static long ComputeStride(const gfxIntSize&, gfxImageFormat); protected: gfxImageSurface(); void InitWithData(unsigned char *aData, const gfxIntSize& aSize, @@ -99,7 +100,6 @@ protected: void InitFromSurface(cairo_surface_t *csurf); long ComputeStride() const { return ComputeStride(mSize, mFormat); } - static long ComputeStride(const gfxIntSize&, gfxImageFormat); void MakeInvalid(); diff --git a/gfx/thebes/gfxSharedImageSurface.cpp b/gfx/thebes/gfxSharedImageSurface.cpp deleted file mode 100644 index d86687c504c..00000000000 --- a/gfx/thebes/gfxSharedImageSurface.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// vim:set ts=4 sts=4 sw=4 et cin: -/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "base/basictypes.h" -#include "gfxSharedImageSurface.h" -#include "cairo.h" - -#define MOZ_ALIGN_WORD(x) (((x) + 3) & ~3) - -using namespace mozilla::ipc; - -static const cairo_user_data_key_t SHM_KEY = {0}; - -struct SharedImageInfo { - int32_t width; - int32_t height; - int32_t format; -}; - -static SharedImageInfo* -GetShmInfoPtr(const Shmem& aShmem) -{ - return reinterpret_cast - (aShmem.get() + aShmem.Size() - sizeof(SharedImageInfo)); -} - -gfxSharedImageSurface::~gfxSharedImageSurface() -{ - MOZ_COUNT_DTOR(gfxSharedImageSurface); -} - -/*static*/ bool -gfxSharedImageSurface::IsSharedImage(gfxASurface* aSurface) -{ - return (aSurface - && aSurface->GetType() == gfxASurface::SurfaceTypeImage - && aSurface->GetData(&SHM_KEY)); -} - -gfxSharedImageSurface::gfxSharedImageSurface(const gfxIntSize& aSize, - gfxImageFormat aFormat, - const Shmem& aShmem) -{ - MOZ_COUNT_CTOR(gfxSharedImageSurface); - - mSize = aSize; - mFormat = aFormat; - mStride = ComputeStride(aSize, aFormat); - mShmem = aShmem; - mData = aShmem.get(); - cairo_surface_t *surface = - cairo_image_surface_create_for_data(mData, - (cairo_format_t)mFormat, - mSize.width, - mSize.height, - mStride); - if (surface) { - cairo_surface_set_user_data(surface, &SHM_KEY, this, NULL); - } - Init(surface); -} - -void -gfxSharedImageSurface::WriteShmemInfo() -{ - SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem); - shmInfo->width = mSize.width; - shmInfo->height = mSize.height; - shmInfo->format = mFormat; -} - -/*static*/ size_t -gfxSharedImageSurface::GetAlignedSize(const gfxIntSize& aSize, long aStride) -{ - return MOZ_ALIGN_WORD(sizeof(SharedImageInfo) + aSize.height * aStride); -} - -/*static*/ already_AddRefed -gfxSharedImageSurface::Open(const Shmem& aShmem) -{ - SharedImageInfo* shmInfo = GetShmInfoPtr(aShmem); - gfxIntSize size(shmInfo->width, shmInfo->height); - if (!CheckSurfaceSize(size)) - return nullptr; - - nsRefPtr s = - new gfxSharedImageSurface(size, - (gfxImageFormat)shmInfo->format, - aShmem); - // We didn't create this Shmem and so don't free it on errors - return (s->CairoStatus() != 0) ? nullptr : s.forget(); -} diff --git a/gfx/thebes/gfxSharedImageSurface.h b/gfx/thebes/gfxSharedImageSurface.h index bdceb82b1cf..fe6c32f66d7 100644 --- a/gfx/thebes/gfxSharedImageSurface.h +++ b/gfx/thebes/gfxSharedImageSurface.h @@ -7,104 +7,18 @@ #ifndef GFX_SHARED_IMAGESURFACE_H #define GFX_SHARED_IMAGESURFACE_H -#include "mozilla/ipc/Shmem.h" -#include "mozilla/ipc/SharedMemory.h" - -#include "gfxASurface.h" -#include "gfxImageSurface.h" - -class THEBES_API gfxSharedImageSurface : public gfxImageSurface { - typedef mozilla::ipc::SharedMemory SharedMemory; - typedef mozilla::ipc::Shmem Shmem; - -public: - virtual ~gfxSharedImageSurface(); - - /** - * Return a new gfxSharedImageSurface around a shmem segment newly - * allocated by this function. |aAllocator| is the object used to - * allocate the new shmem segment. Null is returned if creating - * the surface failed. - * - * NB: the *caller* is responsible for freeing the Shmem allocated - * by this function. - */ - template - static already_AddRefed - Create(ShmemAllocator* aAllocator, - const gfxIntSize& aSize, - gfxImageFormat aFormat, - SharedMemory::SharedMemoryType aShmType = SharedMemory::TYPE_BASIC) - { - return Create(aAllocator, aSize, aFormat, aShmType); - } - - /** - * Return a new gfxSharedImageSurface that wraps a shmem segment - * already created by the Create() above. Bad things will happen - * if an attempt is made to wrap any other shmem segment. Null is - * returned if creating the surface failed. - */ - static already_AddRefed - Open(const Shmem& aShmem); - - template - static already_AddRefed - CreateUnsafe(ShmemAllocator* aAllocator, - const gfxIntSize& aSize, - gfxImageFormat aFormat, - SharedMemory::SharedMemoryType aShmType = SharedMemory::TYPE_BASIC) - { - return Create(aAllocator, aSize, aFormat, aShmType); - } - - Shmem& GetShmem() { return mShmem; } - - static bool IsSharedImage(gfxASurface *aSurface); +#include "gfxBaseSharedMemorySurface.h" +class gfxSharedImageSurface : public gfxBaseSharedMemorySurface +{ + typedef gfxBaseSharedMemorySurface Super; + friend class gfxBaseSharedMemorySurface; private: - gfxSharedImageSurface(const gfxIntSize&, gfxImageFormat, const Shmem&); - - void WriteShmemInfo(); - - static size_t GetAlignedSize(const gfxIntSize&, long aStride); - - template - static already_AddRefed - Create(ShmemAllocator* aAllocator, - const gfxIntSize& aSize, - gfxImageFormat aFormat, - SharedMemory::SharedMemoryType aShmType) - { - if (!CheckSurfaceSize(aSize)) - return nullptr; - - Shmem shmem; - long stride = ComputeStride(aSize, aFormat); - size_t size = GetAlignedSize(aSize, stride); - if (!Unsafe) { - if (!aAllocator->AllocShmem(size, aShmType, &shmem)) - return nullptr; - } else { - if (!aAllocator->AllocUnsafeShmem(size, aShmType, &shmem)) - return nullptr; - } - - nsRefPtr s = - new gfxSharedImageSurface(aSize, aFormat, shmem); - if (s->CairoStatus() != 0) { - aAllocator->DeallocShmem(shmem); - return nullptr; - } - s->WriteShmemInfo(); - return s.forget(); - } - - Shmem mShmem; - - // Calling these is very bad, disallow it - gfxSharedImageSurface(const gfxSharedImageSurface&); - gfxSharedImageSurface& operator=(const gfxSharedImageSurface&); + gfxSharedImageSurface(const gfxIntSize& aSize, long aStride, + gfxASurface::gfxImageFormat aFormat, + const mozilla::ipc::Shmem& aShmem) + : Super(aSize, aStride, aFormat, aShmem) + {} }; #endif /* GFX_SHARED_IMAGESURFACE_H */ From b5b0a2f7d2a7e7b958d7a1f83ebb1525966b0956 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Wed, 7 Nov 2012 19:56:56 +1300 Subject: [PATCH 03/77] Bug 808469 - Use gfxQuartzSurface for OMTC drawing on mac. r=cjones --- gfx/layers/Makefile.in | 4 ++ gfx/layers/ipc/ShadowLayerUtils.h | 4 ++ gfx/layers/ipc/ShadowLayerUtilsMac.cpp | 94 ++++++++++++++++++++++++++ gfx/thebes/Makefile.in | 1 + gfx/thebes/gfxSharedQuartzSurface.h | 25 +++++++ 5 files changed, 128 insertions(+) create mode 100644 gfx/layers/ipc/ShadowLayerUtilsMac.cpp create mode 100644 gfx/thebes/gfxSharedQuartzSurface.h diff --git a/gfx/layers/Makefile.in b/gfx/layers/Makefile.in index 9f8c14bb40f..3ea24a4af3b 100644 --- a/gfx/layers/Makefile.in +++ b/gfx/layers/Makefile.in @@ -172,6 +172,10 @@ EXPORTS_mozilla/layers += ShadowLayerUtilsD3D10.h DEFINES += -DMOZ_ENABLE_D3D10_LAYER endif +ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa) +CPPSRCS += ShadowLayerUtilsMac.cpp +endif + # NB: Gralloc is available on other platforms that use the android GL # libraries, but only Gonk is able to use it reliably because Gecko # has full system permissions there. diff --git a/gfx/layers/ipc/ShadowLayerUtils.h b/gfx/layers/ipc/ShadowLayerUtils.h index 00acd2caf91..4d66cef624b 100644 --- a/gfx/layers/ipc/ShadowLayerUtils.h +++ b/gfx/layers/ipc/ShadowLayerUtils.h @@ -16,6 +16,10 @@ # include "mozilla/layers/ShadowLayerUtilsD3D10.h" #endif +#if defined(XP_MACOSX) +#define MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS +#endif + #if defined(MOZ_X11) # include "mozilla/layers/ShadowLayerUtilsX11.h" #else diff --git a/gfx/layers/ipc/ShadowLayerUtilsMac.cpp b/gfx/layers/ipc/ShadowLayerUtilsMac.cpp new file mode 100644 index 00000000000..0312e8c9bf0 --- /dev/null +++ b/gfx/layers/ipc/ShadowLayerUtilsMac.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/PLayers.h" +#include "mozilla/layers/ShadowLayers.h" + +#include "gfxPlatform.h" + +#include "gfxSharedQuartzSurface.h" + +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +bool +ShadowLayerForwarder::PlatformAllocBuffer(const gfxIntSize& aSize, + gfxASurface::gfxContentType aContent, + uint32_t aCaps, + SurfaceDescriptor* aBuffer) +{ + return false; +} + +/*static*/ already_AddRefed +ShadowLayerForwarder::PlatformOpenDescriptor(OpenMode aMode, + const SurfaceDescriptor& aSurface) +{ + if (SurfaceDescriptor::TShmem != aSurface.type()) { + return nullptr; + } + return gfxSharedQuartzSurface::Open(aSurface.get_Shmem()); +} + +/*static*/ bool +ShadowLayerForwarder::PlatformCloseDescriptor(const SurfaceDescriptor& aDescriptor) +{ + return false; +} + +/*static*/ bool +ShadowLayerForwarder::PlatformGetDescriptorSurfaceContentType( + const SurfaceDescriptor& aDescriptor, OpenMode aMode, + gfxContentType* aContent, + gfxASurface** aSurface) +{ + return false; +} + +/*static*/ bool +ShadowLayerForwarder::PlatformGetDescriptorSurfaceSize( + const SurfaceDescriptor& aDescriptor, OpenMode aMode, + gfxIntSize* aSize, + gfxASurface** aSurface) +{ + return false; +} + +bool +ShadowLayerForwarder::PlatformDestroySharedSurface(SurfaceDescriptor* aSurface) +{ + return false; +} + +/*static*/ void +ShadowLayerForwarder::PlatformSyncBeforeUpdate() +{ +} + +/*static*/ void +ShadowLayerManager::PlatformSyncBeforeReplyUpdate() +{ +} + +bool +ShadowLayerManager::PlatformDestroySharedSurface(SurfaceDescriptor*) +{ + return false; +} + +/*static*/ already_AddRefed +ShadowLayerManager::OpenDescriptorForDirectTexturing(GLContext*, + const SurfaceDescriptor&, + GLenum) +{ + return nullptr; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/thebes/Makefile.in b/gfx/thebes/Makefile.in index f49c74d4836..7f0a60d28aa 100644 --- a/gfx/thebes/Makefile.in +++ b/gfx/thebes/Makefile.in @@ -51,6 +51,7 @@ EXPORTS = \ nsSurfaceTexture.h \ gfxBaseSharedMemorySurface.h \ gfxSharedImageSurface.h \ + gfxSharedQuartzSurface.h \ gfxReusableSurfaceWrapper.h \ gfxSVGGlyphs.h \ $(NULL) diff --git a/gfx/thebes/gfxSharedQuartzSurface.h b/gfx/thebes/gfxSharedQuartzSurface.h new file mode 100644 index 00000000000..d97648a4443 --- /dev/null +++ b/gfx/thebes/gfxSharedQuartzSurface.h @@ -0,0 +1,25 @@ +// vim:set ts=4 sts=4 sw=4 et cin: +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SHARED_QUARTZSURFACE_H +#define GFX_SHARED_QUARTZSURFACE_H + +#include "gfxBaseSharedMemorySurface.h" +#include "gfxQuartzSurface.h" + +class gfxSharedQuartzSurface : public gfxBaseSharedMemorySurface +{ + typedef gfxBaseSharedMemorySurface Super; + friend class gfxBaseSharedMemorySurface; +private: + gfxSharedQuartzSurface(const gfxIntSize& aSize, long aStride, + gfxASurface::gfxImageFormat aFormat, + const mozilla::ipc::Shmem& aShmem) + : Super(aSize, aStride, aFormat, aShmem) + {} +}; + +#endif /* GFX_SHARED_QUARTZSURFACE_H */ From 4a08c209ba988e6797166f0a9952e6a146f44f88 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Wed, 7 Nov 2012 19:56:57 +1300 Subject: [PATCH 04/77] Bug 805948 - Use the local perspective value instead of trying to retrieve it from the nsStyleDisplay again. r=cjones --- layout/base/nsDisplayList.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 4b001647995..1c799eaf4f6 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -3752,8 +3752,7 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const nsIFrame* aFrame, if (nsLayoutUtils::Are3DTransformsEnabled() && perspectiveCoord > 0.0) { gfx3DMatrix perspective; perspective._34 = - -1.0 / NSAppUnitsToFloatPixels(parentDisp->mChildPerspective.GetCoordValue(), - aAppUnitsPerPixel); + -1.0 / NSAppUnitsToFloatPixels(perspectiveCoord, aAppUnitsPerPixel); /* At the point when perspective is applied, we have been translated to the transform origin. * The translation to the perspective origin is the difference between these values. */ From f8b7383acb57d824768bf3cff2f9934fcd82a76c Mon Sep 17 00:00:00 2001 From: Shian-Yow Wu Date: Wed, 7 Nov 2012 15:12:52 +0800 Subject: [PATCH 05/77] Bug 809006 - B2G Network Manager: Unable to set default route and DNS when required system properties not available. r=vicamo --- dom/system/gonk/NetworkManager.js | 5 ++++- dom/system/gonk/net_worker.js | 18 +++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/dom/system/gonk/NetworkManager.js b/dom/system/gonk/NetworkManager.js index 17feaf980a5..31e4922a91a 100644 --- a/dom/system/gonk/NetworkManager.js +++ b/dom/system/gonk/NetworkManager.js @@ -389,7 +389,10 @@ NetworkManager.prototype = { let options = { cmd: this.active.dhcp ? "runDHCPAndSetDefaultRouteAndDNS" : "setDefaultRouteAndDNS", ifname: this.active.name, - oldIfname: (oldInterface && oldInterface != this.active) ? oldInterface.name : null + oldIfname: (oldInterface && oldInterface != this.active) ? oldInterface.name : null, + gateway_str: this.active.gateway, + dns1_str: this.active.dns1, + dns2_str: this.active.dns2 }; this.worker.postMessage(options); this.setNetworkProxy(); diff --git a/dom/system/gonk/net_worker.js b/dom/system/gonk/net_worker.js index 65407e99f5e..baf30b4b45c 100644 --- a/dom/system/gonk/net_worker.js +++ b/dom/system/gonk/net_worker.js @@ -123,11 +123,9 @@ function networkInterfaceStatsSuccess(params) { * Name of the network interface. */ function getIFProperties(ifname) { - let gateway_str = libcutils.property_get("net." + ifname + ".gw"); return { ifname: ifname, - gateway: netHelpers.stringToIP(gateway_str), - gateway_str: gateway_str, + gateway_str: libcutils.property_get("net." + ifname + ".gw"), dns1_str: libcutils.property_get("net." + ifname + ".dns1"), dns2_str: libcutils.property_get("net." + ifname + ".dns2"), }; @@ -159,13 +157,15 @@ function setDefaultRouteAndDNS(options) { libnetutils.ifc_remove_default_route(options.oldIfname); } - if (!options.gateway || !options.dns1_str) { - options = getIFProperties(options.ifname); - } + let ifprops = getIFProperties(options.ifname); + let gateway_str = options.gateway_str || ifprops.gateway_str; + let dns1_str = options.dns1_str || ifprops.dns1_str; + let dns2_str = options.dns2_str || ifprops.dns2_str; + let gateway = netHelpers.stringToIP(gateway_str); - libnetutils.ifc_set_default_route(options.ifname, options.gateway); - libcutils.property_set("net.dns1", options.dns1_str); - libcutils.property_set("net.dns2", options.dns2_str); + libnetutils.ifc_set_default_route(options.ifname, gateway); + libcutils.property_set("net.dns1", dns1_str); + libcutils.property_set("net.dns2", dns2_str); // Bump the DNS change property. let dnschange = libcutils.property_get("net.dnschange", "0"); From 66209d86a7490f6bfe8c9e35fc1750bfb0b12992 Mon Sep 17 00:00:00 2001 From: Bas Schouten Date: Wed, 7 Nov 2012 09:29:54 +0100 Subject: [PATCH 06/77] Bug 509052: Add new, faster blurring code. r=derf --- gfx/2d/Blur.cpp | 258 +++++++++++++++++++++++++++++++----- gfx/2d/Blur.h | 8 ++ gfx/2d/BlurSSE2.cpp | 250 ++++++++++++++++++++++++++++++++++ gfx/2d/ImageScalingSSE2.cpp | 14 +- gfx/2d/Makefile.in | 7 +- gfx/2d/SSEHelpers.h | 17 +++ gfx/2d/Tools.h | 58 ++++++++ 7 files changed, 567 insertions(+), 45 deletions(-) create mode 100644 gfx/2d/BlurSSE2.cpp create mode 100644 gfx/2d/SSEHelpers.h diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp index 8def843f3f4..d1f5c9616e9 100644 --- a/gfx/2d/Blur.cpp +++ b/gfx/2d/Blur.cpp @@ -12,6 +12,9 @@ #include "mozilla/Constants.h" #include "mozilla/Util.h" +#include "2D.h" +#include "Tools.h" + using namespace std; namespace mozilla { @@ -311,8 +314,8 @@ SpreadVertical(unsigned char* aInput, } } -static CheckedInt -RoundUpToMultipleOf4(int32_t aVal) +CheckedInt +AlphaBoxBlur::RoundUpToMultipleOf4(int32_t aVal) { CheckedInt val(aVal); @@ -378,10 +381,9 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, if (stride.isValid()) { mStride = stride.value(); - CheckedInt size = CheckedInt(mStride) * mRect.height * - sizeof(unsigned char); + CheckedInt size = CheckedInt(mStride) * mRect.height; if (size.isValid()) { - mData = static_cast(malloc(size.value())); + mData = new uint8_t[size.value()]; memset(mData, 0, size.value()); } } @@ -405,7 +407,7 @@ AlphaBoxBlur::AlphaBoxBlur(uint8_t* aData, AlphaBoxBlur::~AlphaBoxBlur() { if (mFreeData) { - free(mData); + delete [] mData; } } @@ -455,42 +457,236 @@ AlphaBoxBlur::Blur() if (mBlurRadius != IntSize(0,0) || mSpreadRadius != IntSize(0,0)) { int32_t stride = GetStride(); - // No need to use CheckedInt here - we have validated it in the constructor. - size_t szB = stride * GetSize().height * sizeof(unsigned char); - unsigned char* tmpData = static_cast(malloc(szB)); - if (!tmpData) - return; // OOM - - memset(tmpData, 0, szB); + IntSize size = GetSize(); if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) { + // No need to use CheckedInt here - we have validated it in the constructor. + size_t szB = stride * size.height; + unsigned char* tmpData = new uint8_t[szB]; + + memset(tmpData, 0, szB); + SpreadHorizontal(mData, tmpData, mSpreadRadius.width, GetSize().width, GetSize().height, stride, mSkipRect); SpreadVertical(tmpData, mData, mSpreadRadius.height, GetSize().width, GetSize().height, stride, mSkipRect); + + delete [] tmpData; } - if (mBlurRadius.width > 0) { - int32_t lobes[3][2]; - ComputeLobes(mBlurRadius.width, lobes); - BoxBlurHorizontal(mData, tmpData, lobes[0][0], lobes[0][1], stride, GetSize().height, mSkipRect); - BoxBlurHorizontal(tmpData, mData, lobes[1][0], lobes[1][1], stride, GetSize().height, mSkipRect); - BoxBlurHorizontal(mData, tmpData, lobes[2][0], lobes[2][1], stride, GetSize().height, mSkipRect); + int32_t horizontalLobes[3][2]; + ComputeLobes(mBlurRadius.width, horizontalLobes); + int32_t verticalLobes[3][2]; + ComputeLobes(mBlurRadius.height, verticalLobes); + + // We want to allow for some extra space on the left for alignment reasons. + int32_t maxLeftLobe = RoundUpToMultipleOf4(horizontalLobes[0][0] + 1).value(); + + IntSize integralImageSize(size.width + maxLeftLobe + horizontalLobes[1][1], + size.height + verticalLobes[0][0] + verticalLobes[1][1] + 1); + +#ifdef IS_BIG_ENDIAN + const bool cIsBigEndian = true; +#else + const bool cIsBigEndian = false; +#endif + + if (cIsBigEndian || (integralImageSize.width * integralImageSize.height) > (1 << 24)) { + // Fallback to old blurring code when the surface is so large it may + // overflow our integral image! + + // No need to use CheckedInt here - we have validated it in the constructor. + size_t szB = stride * size.height; + unsigned char* tmpData = new uint8_t[szB]; + + memset(tmpData, 0, szB); + + if (mBlurRadius.width > 0) { + BoxBlurHorizontal(mData, tmpData, horizontalLobes[0][0], horizontalLobes[0][1], stride, GetSize().height, mSkipRect); + BoxBlurHorizontal(tmpData, mData, horizontalLobes[1][0], horizontalLobes[1][1], stride, GetSize().height, mSkipRect); + BoxBlurHorizontal(mData, tmpData, horizontalLobes[2][0], horizontalLobes[2][1], stride, GetSize().height, mSkipRect); + } else { + uint8_t *tmp = mData; + mData = tmpData; + tmpData = tmp; + } + if (mBlurRadius.height > 0) { + BoxBlurVertical(tmpData, mData, verticalLobes[0][0], verticalLobes[0][1], stride, GetSize().height, mSkipRect); + BoxBlurVertical(mData, tmpData, verticalLobes[1][0], verticalLobes[1][1], stride, GetSize().height, mSkipRect); + BoxBlurVertical(tmpData, mData, verticalLobes[2][0], verticalLobes[2][1], stride, GetSize().height, mSkipRect); + } else { + uint8_t *tmp = mData; + mData = tmpData; + tmpData = tmp; + } + + delete [] tmpData; } else { - memcpy(tmpData, mData, stride * GetSize().height); - } + size_t integralImageStride = GetAlignedStride<16>(integralImageSize.width * 4); - if (mBlurRadius.height > 0) { - int32_t lobes[3][2]; - ComputeLobes(mBlurRadius.height, lobes); - BoxBlurVertical(tmpData, mData, lobes[0][0], lobes[0][1], stride, GetSize().height, mSkipRect); - BoxBlurVertical(mData, tmpData, lobes[1][0], lobes[1][1], stride, GetSize().height, mSkipRect); - BoxBlurVertical(tmpData, mData, lobes[2][0], lobes[2][1], stride, GetSize().height, mSkipRect); - } else { - memcpy(mData, tmpData, stride * GetSize().height); - } + AlignedArray integralImage((integralImageStride / 4) * integralImageSize.height); - free(tmpData); +#ifdef USE_SSE2 + if (Factory::HasSSE2()) { + BoxBlur_SSE2(horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], + verticalLobes[0][1], integralImage, integralImageStride); + BoxBlur_SSE2(horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], + verticalLobes[1][1], integralImage, integralImageStride); + BoxBlur_SSE2(horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], + verticalLobes[2][1], integralImage, integralImageStride); + } else +#endif + { + BoxBlur_C(horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], + verticalLobes[0][1], integralImage, integralImageStride); + BoxBlur_C(horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], + verticalLobes[1][1], integralImage, integralImageStride); + BoxBlur_C(horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], + verticalLobes[2][1], integralImage, integralImageStride); + } + } + } +} + +MOZ_ALWAYS_INLINE void +GenerateIntegralRow(uint32_t *aDest, const uint8_t *aSource, uint32_t *aPreviousRow, + const uint32_t &aSourceWidth, const uint32_t &aLeftInflation, const uint32_t &aRightInflation) +{ + uint32_t currentRowSum = 0; + uint32_t pixel = aSource[0]; + for (uint32_t x = 0; x < aLeftInflation; x++) { + currentRowSum += pixel; + *aDest++ = currentRowSum + *aPreviousRow++; + } + for (uint32_t x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x += 4) { + uint32_t alphaValues = *(uint32_t*)(aSource + (x - aLeftInflation)); + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + } + pixel = aSource[aSourceWidth - 1]; + for (uint32_t x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += pixel; + *aDest++ = currentRowSum + *aPreviousRow++; + } +} + +MOZ_ALWAYS_INLINE void +GenerateIntegralImage_C(int32_t aLeftInflation, int32_t aRightInflation, + int32_t aTopInflation, int32_t aBottomInflation, + uint32_t *aIntegralImage, size_t aIntegralImageStride, + uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) +{ + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + memset(aIntegralImage, 0, aIntegralImageStride); + + GenerateIntegralRow(aIntegralImage, aSource, aIntegralImage, + aSize.width, aLeftInflation, aRightInflation); + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t *intFirstRow = aIntegralImage; + + GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource, aIntegralImage + (y - 1) * stride32bit, + aSize.width, aLeftInflation, aRightInflation); } + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource + aSourceStride * (y - aTopInflation), + aIntegralImage + (y - 1) * stride32bit, aSize.width, aLeftInflation, aRightInflation); + } + + if (aBottomInflation) { + for (int y = (aSize.height + aTopInflation); y < integralImageSize.height; y++) { + GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource + ((aSize.height - 1) * aSourceStride), + aIntegralImage + (y - 1) * stride32bit, + aSize.width, aLeftInflation, aRightInflation); + } + } +} + +/** + * Attempt to do an in-place box blur using an integral image. + */ +void +AlphaBoxBlur::BoxBlur_C(int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aTopLobe, + int32_t aBottomLobe, + uint32_t *aIntegralImage, + size_t aIntegralImageStride) +{ + IntSize size = GetSize(); + + MOZ_ASSERT(size.width > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t stride32bit = aIntegralImageStride / 4; + + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_C(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, mData, + mStride, size); + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + // Storing these locally makes this about 30% faster! Presumably the compiler + // can't be sure we're not altering the member variables in this loop. + IntRect skipRect = mSkipRect; + uint8_t *data = mData; + int32_t stride = mStride; + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + + uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * stride32bit - aLeftLobe); + uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * stride32bit + aRightLobe); + uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * stride32bit + aRightLobe); + uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * stride32bit - aLeftLobe); + + for (int32_t x = 0; x < size.width; x++) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 1; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + int32_t topLeft = topLeftBase[x]; + int32_t topRight = topRightBase[x]; + int32_t bottomRight = bottomRightBase[x]; + int32_t bottomLeft = bottomLeftBase[x]; + + uint32_t value = bottomRight - topRight - bottomLeft; + value += topLeft; + + data[stride * y + x] = (uint64_t(reciprocal) * value) >> 32; + } + } } /** diff --git a/gfx/2d/Blur.h b/gfx/2d/Blur.h index 24f32941904..d425d6ee9f2 100644 --- a/gfx/2d/Blur.h +++ b/gfx/2d/Blur.h @@ -7,6 +7,7 @@ #include "mozilla/gfx/Rect.h" #include "mozilla/gfx/Point.h" +#include "mozilla/CheckedInt.h" namespace mozilla { namespace gfx { @@ -114,6 +115,13 @@ public: private: + void BoxBlur_C(int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); + void BoxBlur_SSE2(int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); + + static CheckedInt RoundUpToMultipleOf4(int32_t aVal); + /** * A rect indicating the area where blurring is unnecessary, and the blur * algorithm should skip over it. diff --git a/gfx/2d/BlurSSE2.cpp b/gfx/2d/BlurSSE2.cpp new file mode 100644 index 00000000000..d53153d49be --- /dev/null +++ b/gfx/2d/BlurSSE2.cpp @@ -0,0 +1,250 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Blur.h" + +#include "SSEHelpers.h" + +#include + +namespace mozilla { +namespace gfx { + +MOZ_ALWAYS_INLINE +uint32_t DivideAndPack(__m128i aValues, __m128i aDivisor, __m128i aMask) +{ + __m128i multiplied = _mm_srli_epi64(_mm_mul_epu32(aValues, aDivisor), 32); // 00p300p1 + multiplied = _mm_or_si128(multiplied, _mm_and_si128(_mm_mul_epu32(_mm_srli_epi64(aValues, 32), aDivisor), + aMask)); // p4p3p2p1 + __m128i final = _mm_packus_epi16(_mm_packs_epi32(multiplied, _mm_setzero_si128()), _mm_setzero_si128()); + + return _mm_cvtsi128_si32(final); +} + +MOZ_ALWAYS_INLINE +void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource, + int32_t aSourceWidth, int32_t aLeftInflation, + int32_t aRightInflation) +{ + int32_t currentRowSum = 0; + + for (int x = 0; x < aLeftInflation; x++) { + currentRowSum += aSource[0]; + aDest[x] = currentRowSum; + } + for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { + currentRowSum += aSource[(x - aLeftInflation)]; + aDest[x] = currentRowSum; + } + for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += aSource[aSourceWidth - 1]; + aDest[x] = currentRowSum; + } +} + +// This function calculates an integral of four pixels stored in the 4 +// 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns +// { 30, 80, 160, 260 }. This seems to be the fastest way to do this after +// much testing. +MOZ_ALWAYS_INLINE +__m128i AccumulatePixelSums(__m128i aPixels) +{ + __m128i sumPixels = aPixels; + __m128i currentPixels = _mm_slli_si128(aPixels, 4); + sumPixels = _mm_add_epi32(sumPixels, currentPixels); + currentPixels = _mm_unpacklo_epi64(_mm_setzero_si128(), sumPixels); + + return _mm_add_epi32(sumPixels, currentPixels); +} + +MOZ_ALWAYS_INLINE void +GenerateIntegralImage_SSE2(int32_t aLeftInflation, int32_t aRightInflation, + int32_t aTopInflation, int32_t aBottomInflation, + uint32_t *aIntegralImage, size_t aIntegralImageStride, + uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) +{ + MOZ_ASSERT(!(aLeftInflation & 3)); + + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation); + + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t *intFirstRow = aIntegralImage; + + for (int x = 0; x < integralImageSize.width; x += 4) { + __m128i firstRow = _mm_load_si128((__m128i*)(intFirstRow + x)); + __m128i previousRow = _mm_load_si128((__m128i*)(intPrevRow + x)); + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(firstRow, previousRow)); + } + } + + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + __m128i currentRowSum = _mm_setzero_si128(); + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation); + + uint32_t pixel = sourceRow[0]; + for (int x = 0; x < aLeftInflation; x += 4) { + __m128i sumPixels = AccumulatePixelSums(_mm_shuffle_epi32(_mm_set1_epi32(pixel), _MM_SHUFFLE(0, 0, 0, 0))); + + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); + + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { + uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation)); + + // It's important to shuffle here. When we exit this loop currentRowSum + // has to be set to sumPixels, so that the following loop can get the + // correct pixel for the currentRowSum. The highest order pixel in + // currentRowSum could've originated from accumulation in the stride. + currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); + + __m128i sumPixels = AccumulatePixelSums(_mm_unpacklo_epi16(_mm_unpacklo_epi8( _mm_set1_epi32(pixels), _mm_setzero_si128()), _mm_setzero_si128())); + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = sumPixels; + + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + + pixel = sourceRow[aSize.width - 1]; + int x = (aSize.width + aLeftInflation); + if ((aSize.width & 3)) { + // Deal with unaligned portion. Get the correct pixel from currentRowSum, + // see explanation above. + uint32_t intCurrentRowSum = ((uint32_t*)¤tRowSum)[(aSize.width % 4) - 1]; + for (; x < integralImageSize.width; x++) { + // We could be unaligned here! + if (!(x & 3)) { + // aligned! + currentRowSum = _mm_set1_epi32(intCurrentRowSum); + break; + } + intCurrentRowSum += pixel; + intRow[x] = intPrevRow[x] + intCurrentRowSum; + } + } else { + currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); + } + for (; x < integralImageSize.width; x += 4) { + __m128i sumPixels = AccumulatePixelSums(_mm_set1_epi32(pixel)); + + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); + + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + } + + if (aBottomInflation) { + // Store the last valid row of our source image in the last row of + // our integral image. This will be overwritten with the correct values + // in the upcoming loop. + LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit, + aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation); + + + for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) { + __m128i *intRow = (__m128i*)(aIntegralImage + (y * stride32bit)); + __m128i *intPrevRow = (__m128i*)(aIntegralImage + (y - 1) * stride32bit); + __m128i *intLastRow = (__m128i*)(aIntegralImage + (integralImageSize.height - 1) * stride32bit); + + for (int x = 0; x < integralImageSize.width; x += 4) { + _mm_store_si128(intRow + (x / 4), + _mm_add_epi32(_mm_load_si128(intLastRow + (x / 4)), + _mm_load_si128(intPrevRow + (x / 4)))); + } + } + } +} + +/** + * Attempt to do an in-place box blur using an integral image. + */ +void +AlphaBoxBlur::BoxBlur_SSE2(int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aTopLobe, + int32_t aBottomLobe, + uint32_t *aIntegralImage, + size_t aIntegralImageStride) +{ + IntSize size = GetSize(); + + MOZ_ASSERT(size.height > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t stride32bit = aIntegralImageStride / 4; + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_SSE2(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, mData, + mStride, size); + + __m128i divisor = _mm_set1_epi32(reciprocal); + __m128i mask = _mm_setr_epi32(0x0, 0xffffffff, 0x0, 0xffffffff); + + // This points to the start of the rectangle within the IntegralImage that overlaps + // the surface being blurred. + uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + IntRect skipRect = mSkipRect; + int32_t stride = mStride; + uint8_t *data = mData; + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + + uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + + for (int32_t x = 0; x < size.width; x += 4) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 4; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + __m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + __m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + __m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + __m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + + __m128i values = _mm_add_epi32(_mm_sub_epi32(_mm_sub_epi32(bottomRight, topRight), bottomLeft), topLeft); + + *(uint32_t*)(data + stride * y + x) = DivideAndPack(values, divisor, mask); + } + } + +} + +} +} diff --git a/gfx/2d/ImageScalingSSE2.cpp b/gfx/2d/ImageScalingSSE2.cpp index cad5eb85aae..f87653b4cab 100644 --- a/gfx/2d/ImageScalingSSE2.cpp +++ b/gfx/2d/ImageScalingSSE2.cpp @@ -6,8 +6,7 @@ #include "ImageScaling.h" #include "mozilla/Attributes.h" -#include -#include +#include "SSEHelpers.h" /* The functions below use the following system for averaging 4 pixels: * @@ -108,17 +107,6 @@ MOZ_ALWAYS_INLINE __m128i avg_sse2_8x1_4x1(__m128i a, __m128i b) return _mm_not_si128(_mm_avg_epu8(_mm_not_si128(a), _mm_not_si128(b))); } -/* Before Nehalem _mm_loadu_si128 could be very slow, this trick is a little - * faster. Once enough people are on architectures where _mm_loadu_si128 is - * fast we can migrate to it. - */ -MOZ_ALWAYS_INLINE __m128i loadUnaligned128(const __m128i *aSource) -{ - // Yes! We use uninitialized memory here, we'll overwrite it though! - __m128 res = _mm_loadl_pi(_mm_set1_ps(0), (const __m64*)aSource); - return _mm_castps_si128(_mm_loadh_pi(res, ((const __m64*)(aSource)) + 1)); -} - MOZ_ALWAYS_INLINE uint32_t Avg2x2(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { uint32_t sum = a ^ b ^ c; diff --git a/gfx/2d/Makefile.in b/gfx/2d/Makefile.in index 6959f5d2c32..376f72cfb90 100644 --- a/gfx/2d/Makefile.in +++ b/gfx/2d/Makefile.in @@ -116,7 +116,10 @@ endif ifneq (,$(INTEL_ARCHITECTURE)) # VC2005 doesn't support _mm_castsi128_ps, so SSE2 is turned off ifneq (1400,$(_MSC_VER)) -CPPSRCS += ImageScalingSSE2.cpp +CPPSRCS += \ + ImageScalingSSE2.cpp \ + BlurSSE2.cpp \ + $(NULL) DEFINES += -DUSE_SSE2 endif endif @@ -161,10 +164,12 @@ DEFINES := $(filter-out -DUNICODE -D_UNICODE,$(DEFINES)) ifneq (,$(INTEL_ARCHITECTURE)) ifdef GNU_CC ImageScalingSSE2.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 +BlurSSE2.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 endif ifdef SOLARIS_SUNPRO_CXX ImageScalingSSE2.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 +BlurSSE2.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 endif endif diff --git a/gfx/2d/SSEHelpers.h b/gfx/2d/SSEHelpers.h new file mode 100644 index 00000000000..61b53d86eac --- /dev/null +++ b/gfx/2d/SSEHelpers.h @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include + +/* Before Nehalem _mm_loadu_si128 could be very slow, this trick is a little + * faster. Once enough people are on architectures where _mm_loadu_si128 is + * fast we can migrate to it. + */ +MOZ_ALWAYS_INLINE __m128i loadUnaligned128(const __m128i *aSource) +{ + // Yes! We use uninitialized memory here, we'll overwrite it though! + __m128 res = _mm_loadl_pi(_mm_set1_ps(0), (const __m64*)aSource); + return _mm_castps_si128(_mm_loadh_pi(res, ((const __m64*)(aSource)) + 1)); +} diff --git a/gfx/2d/Tools.h b/gfx/2d/Tools.h index d645c2b219d..60e89fa0ea2 100644 --- a/gfx/2d/Tools.h +++ b/gfx/2d/Tools.h @@ -81,6 +81,64 @@ BytesPerPixel(SurfaceFormat aFormat) } } +template +struct AlignedArray +{ + AlignedArray() + : mStorage(nullptr) + , mPtr(nullptr) + { + } + + MOZ_ALWAYS_INLINE AlignedArray(size_t aSize) + : mStorage(nullptr) + { + Realloc(aSize); + } + + MOZ_ALWAYS_INLINE ~AlignedArray() + { + delete [] mStorage; + } + + void Dealloc() + { + delete [] mStorage; + mStorage = mPtr = nullptr; + } + + MOZ_ALWAYS_INLINE void Realloc(size_t aSize) + { + delete [] mStorage; + mStorage = new T[aSize + (alignment - 1)]; + if (uintptr_t(mStorage) % alignment) { + // Our storage does not start at a -byte boundary. Make sure mData does! + mPtr = (uint32_t*)(uintptr_t(mStorage) + + (alignment - (uintptr_t(mStorage) % alignment))); + } else { + mPtr = mStorage; + } + } + + MOZ_ALWAYS_INLINE operator T*() + { + return mPtr; + } + + T *mStorage; + T *mPtr; +}; + +template +int32_t GetAlignedStride(int32_t aStride) +{ + if (aStride % alignment) { + return aStride + (alignment - (aStride % alignment)); + } + + return aStride; +} + } } From 4c5557e3c7b155f3d30c89717d55e38baddc80e0 Mon Sep 17 00:00:00 2001 From: Dave Hylands Date: Wed, 7 Nov 2012 01:03:52 -0800 Subject: [PATCH 07/77] Bug 794599 - Backout 847cedab81ae due to debug orange --- b2g/chrome/content/settings.js | 7 +------ xpcom/base/nsConsoleService.cpp | 15 +-------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/b2g/chrome/content/settings.js b/b2g/chrome/content/settings.js index ea7141e76be..9c730ef2274 100644 --- a/b2g/chrome/content/settings.js +++ b/b2g/chrome/content/settings.js @@ -92,12 +92,6 @@ for each (let [setting, defaultValue, streamType] in audioSettings) { })(setting, defaultValue, streamType); } -// =================== Console ====================== - -SettingsListener.observe('debug.console.enabled', true, function(value) { - Services.prefs.setBoolPref('consoleservice.enabled', value); -}); - // =================== Languages ==================== SettingsListener.observe('language.current', 'en-US', function(value) { Services.prefs.setCharPref('general.useragent.locale', value); @@ -120,6 +114,7 @@ SettingsListener.observe('language.current', 'en-US', function(value) { shell.start(); }); + // =================== RIL ==================== (function RILSettingsToPrefs() { let strPrefs = ['ril.data.mmsc', 'ril.data.mmsproxy']; diff --git a/xpcom/base/nsConsoleService.cpp b/xpcom/base/nsConsoleService.cpp index 2909f3b0584..137cb9ef96e 100644 --- a/xpcom/base/nsConsoleService.cpp +++ b/xpcom/base/nsConsoleService.cpp @@ -19,8 +19,6 @@ #include "nsConsoleMessage.h" #include "nsIClassInfoImpl.h" -#include "mozilla/Preferences.h" - #if defined(ANDROID) #include #endif @@ -36,8 +34,6 @@ NS_IMPL_CLASSINFO(nsConsoleService, NULL, nsIClassInfo::THREADSAFE | nsIClassInf NS_IMPL_QUERY_INTERFACE1_CI(nsConsoleService, nsIConsoleService) NS_IMPL_CI_INTERFACE_GETTER1(nsConsoleService, nsIConsoleService) -static bool sLoggingEnabled = true; - nsConsoleService::nsConsoleService() : mMessages(nullptr) , mCurrent(0) @@ -75,7 +71,6 @@ nsConsoleService::Init() memset(mMessages, 0, mBufferSize * sizeof(nsIConsoleMessage *)); mListeners.Init(); - Preferences::AddBoolVarCache(&sLoggingEnabled, "consoleservice.enabled", true); return NS_OK; } @@ -135,10 +130,6 @@ nsConsoleService::LogMessage(nsIConsoleMessage *message) if (message == nullptr) return NS_ERROR_INVALID_ARG; - if (!sLoggingEnabled) { - return NS_OK; - } - if (NS_IsMainThread() && mDeliveringMessage) { NS_WARNING("Some console listener threw an error while inside itself. Discarding this message"); return NS_ERROR_FAILURE; @@ -212,11 +203,7 @@ nsConsoleService::LogMessage(nsIConsoleMessage *message) NS_IMETHODIMP nsConsoleService::LogStringMessage(const PRUnichar *message) { - if (!sLoggingEnabled) { - return NS_OK; - } - - nsRefPtr msg(new nsConsoleMessage(message)); + nsConsoleMessage *msg = new nsConsoleMessage(message); return this->LogMessage(msg); } From 4107bd875143747ca31bd6ef504969304dd31ec7 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Sun, 26 Aug 2012 22:36:44 -0700 Subject: [PATCH 08/77] Bug 779997 - Import SoundTouch Library in the tree. r= --- b2g/installer/package-manifest.in | 1 + browser/installer/package-manifest.in | 1 + config/system-headers | 3 +- configure.in | 15 + js/src/config/system-headers | 3 +- media/libsoundtouch/AUTHORS | 4 + media/libsoundtouch/LICENSE | 458 +++++++++++ media/libsoundtouch/Makefile.in | 17 + media/libsoundtouch/README_MOZILLA | 8 + media/libsoundtouch/moz-libsoundtouch.patch | 509 ++++++++++++ media/libsoundtouch/src/AAFilter.cpp | 184 +++++ media/libsoundtouch/src/AAFilter.h | 91 +++ media/libsoundtouch/src/FIFOSampleBuffer.cpp | 274 +++++++ media/libsoundtouch/src/FIFOSampleBuffer.h | 178 ++++ media/libsoundtouch/src/FIFOSamplePipe.h | 234 ++++++ media/libsoundtouch/src/FIRFilter.cpp | 259 ++++++ media/libsoundtouch/src/FIRFilter.h | 145 ++++ media/libsoundtouch/src/Makefile.in | 69 ++ media/libsoundtouch/src/RateTransposer.cpp | 626 ++++++++++++++ media/libsoundtouch/src/RateTransposer.h | 159 ++++ media/libsoundtouch/src/STTypes.h | 159 ++++ media/libsoundtouch/src/SoundTouch.cpp | 501 ++++++++++++ media/libsoundtouch/src/SoundTouch.h | 277 +++++++ media/libsoundtouch/src/TDStretch.cpp | 808 +++++++++++++++++++ media/libsoundtouch/src/TDStretch.h | 268 ++++++ media/libsoundtouch/src/cpu_detect.h | 62 ++ media/libsoundtouch/src/cpu_detect_x86.cpp | 144 ++++ media/libsoundtouch/src/mmx_optimized.cpp | 317 ++++++++ media/libsoundtouch/src/soundtouch.rc | 53 ++ media/libsoundtouch/src/soundtouch_config.h | 19 + media/libsoundtouch/src/sse_optimized.cpp | 361 +++++++++ media/libsoundtouch/update.sh | 40 + mobile/android/installer/package-manifest.in | 1 + mobile/xul/installer/package-manifest.in | 1 + toolkit/content/license.html | 27 + toolkit/library/Makefile.in | 1 + toolkit/mozapps/installer/packager.mk | 1 + toolkit/toolkit-makefiles.sh | 8 + toolkit/toolkit-tiers.mk | 6 + 39 files changed, 6290 insertions(+), 2 deletions(-) create mode 100644 media/libsoundtouch/AUTHORS create mode 100644 media/libsoundtouch/LICENSE create mode 100644 media/libsoundtouch/Makefile.in create mode 100644 media/libsoundtouch/README_MOZILLA create mode 100644 media/libsoundtouch/moz-libsoundtouch.patch create mode 100644 media/libsoundtouch/src/AAFilter.cpp create mode 100644 media/libsoundtouch/src/AAFilter.h create mode 100644 media/libsoundtouch/src/FIFOSampleBuffer.cpp create mode 100644 media/libsoundtouch/src/FIFOSampleBuffer.h create mode 100644 media/libsoundtouch/src/FIFOSamplePipe.h create mode 100644 media/libsoundtouch/src/FIRFilter.cpp create mode 100644 media/libsoundtouch/src/FIRFilter.h create mode 100644 media/libsoundtouch/src/Makefile.in create mode 100644 media/libsoundtouch/src/RateTransposer.cpp create mode 100644 media/libsoundtouch/src/RateTransposer.h create mode 100644 media/libsoundtouch/src/STTypes.h create mode 100644 media/libsoundtouch/src/SoundTouch.cpp create mode 100644 media/libsoundtouch/src/SoundTouch.h create mode 100644 media/libsoundtouch/src/TDStretch.cpp create mode 100644 media/libsoundtouch/src/TDStretch.h create mode 100644 media/libsoundtouch/src/cpu_detect.h create mode 100644 media/libsoundtouch/src/cpu_detect_x86.cpp create mode 100644 media/libsoundtouch/src/mmx_optimized.cpp create mode 100644 media/libsoundtouch/src/soundtouch.rc create mode 100644 media/libsoundtouch/src/soundtouch_config.h create mode 100644 media/libsoundtouch/src/sse_optimized.cpp create mode 100644 media/libsoundtouch/update.sh diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 9399b762c81..8687a1eed2b 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -54,6 +54,7 @@ @BINPATH@/@DLL_PREFIX@xpcom@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ #ifdef XP_MACOSX @BINPATH@/XUL #else diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 52f3f4be673..266b49dba0c 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -54,6 +54,7 @@ @BINPATH@/@DLL_PREFIX@gkmedias@DLL_SUFFIX@ #endif @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ #ifdef MOZ_SHARED_MOZGLUE @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@ #endif diff --git a/config/system-headers b/config/system-headers index f781c8220d0..74fc247fdb0 100644 --- a/config/system-headers +++ b/config/system-headers @@ -981,6 +981,8 @@ plstr.h plarenas.h plarena.h plhash.h +speex/speex_resampler.h +soundtouch/SoundTouch.h #if MOZ_NATIVE_PNG==1 png.h #endif @@ -1053,7 +1055,6 @@ vpx/vpx_encoder.h vpx/vp8cx.h vpx/vp8dx.h sydneyaudio/sydney_audio.h -speex/speex_resampler.h vorbis/codec.h theora/theoradec.h tremor/ivorbiscodec.h diff --git a/configure.in b/configure.in index ef2acbc7a85..5bb372e5a02 100644 --- a/configure.in +++ b/configure.in @@ -4225,6 +4225,7 @@ MOZ_OGG=1 MOZ_RAW= MOZ_SYDNEYAUDIO= MOZ_SPEEX_RESAMPLER=1 +MOZ_SOUNDTOUCH=1 MOZ_CUBEB= MOZ_VORBIS= MOZ_TREMOR= @@ -5580,6 +5581,19 @@ if test -n "$MOZ_SPEEX_RESAMPLER"; then AC_DEFINE(MOZ_SPEEX_RESAMPLER) fi +if test -n "$MOZ_SOUNDTOUCH"; then + AC_DEFINE(MOZ_SOUNDTOUCH) +fi + +if test -z "$GNU_CC" -a "$OS_ARCH" = "WINNT"; then + SOUNDTOUCH_LIBS='$(LIBXUL_DIST)/lib/$(LIB_PREFIX)soundtouch.$(LIB_SUFFIX)' +else + SOUNDTOUCH_LIBS='-lsoundtouch' +fi +AC_SUBST(SOUNDTOUCH_CFLAGS) +AC_SUBST(SOUNDTOUCH_LIBS) +AC_SUBST(SOUNDTOUCH_CONFIG) + if test -n "$MOZ_CUBEB"; then case "$target" in *-android*|*-linuxandroid*) @@ -8653,6 +8667,7 @@ AC_SUBST(MOZ_APP_EXTRA_LIBS) AC_SUBST(MOZ_MEDIA) AC_SUBST(MOZ_SYDNEYAUDIO) AC_SUBST(MOZ_SPEEX_RESAMPLER) +AC_SUBST(MOZ_SOUNDTOUCH) AC_SUBST(MOZ_CUBEB) AC_SUBST(MOZ_WAVE) AC_SUBST(MOZ_VORBIS) diff --git a/js/src/config/system-headers b/js/src/config/system-headers index f781c8220d0..74fc247fdb0 100644 --- a/js/src/config/system-headers +++ b/js/src/config/system-headers @@ -981,6 +981,8 @@ plstr.h plarenas.h plarena.h plhash.h +speex/speex_resampler.h +soundtouch/SoundTouch.h #if MOZ_NATIVE_PNG==1 png.h #endif @@ -1053,7 +1055,6 @@ vpx/vpx_encoder.h vpx/vp8cx.h vpx/vp8dx.h sydneyaudio/sydney_audio.h -speex/speex_resampler.h vorbis/codec.h theora/theoradec.h tremor/ivorbiscodec.h diff --git a/media/libsoundtouch/AUTHORS b/media/libsoundtouch/AUTHORS new file mode 100644 index 00000000000..666081e1952 --- /dev/null +++ b/media/libsoundtouch/AUTHORS @@ -0,0 +1,4 @@ +The SoundTouch Library +Copyright © Olli Parviainen 2001-2012 + +http://www.surina.net/soundtouch/ diff --git a/media/libsoundtouch/LICENSE b/media/libsoundtouch/LICENSE new file mode 100644 index 00000000000..5b2161be204 --- /dev/null +++ b/media/libsoundtouch/LICENSE @@ -0,0 +1,458 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authoried party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/media/libsoundtouch/Makefile.in b/media/libsoundtouch/Makefile.in new file mode 100644 index 00000000000..a8c74390b16 --- /dev/null +++ b/media/libsoundtouch/Makefile.in @@ -0,0 +1,17 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = soundtouch + +DIRS = src \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/media/libsoundtouch/README_MOZILLA b/media/libsoundtouch/README_MOZILLA new file mode 100644 index 00000000000..141860babd4 --- /dev/null +++ b/media/libsoundtouch/README_MOZILLA @@ -0,0 +1,8 @@ +These files are from the SoundTouch library (http://www.surina.net/soundtouch/), +and are extracted from the revision r143 of the svn repository at +https://soundtouch.svn.sourceforge.net/svnroot/soundtouch/trunk. + +The whole library is not used, only the relevant files are imported in the tree, +using the script `update.sh`. Some changes have been made to the files, using +the patch `moz-libsoundtouch.patch`. We also use a custom soundtouch_config.h. + diff --git a/media/libsoundtouch/moz-libsoundtouch.patch b/media/libsoundtouch/moz-libsoundtouch.patch new file mode 100644 index 00000000000..f78ec818561 --- /dev/null +++ b/media/libsoundtouch/moz-libsoundtouch.patch @@ -0,0 +1,509 @@ +unchanged: +--- /src/STTypes.h 2012-08-02 10:04:06.301691592 -0700 ++++ /src/STTypes.h +@@ -42,21 +42,13 @@ + typedef unsigned int uint; + typedef unsigned long ulong; + +-#ifdef __GNUC__ +- // In GCC, include soundtouch_config.h made by config scritps +- #include "soundtouch_config.h" +-#endif +- +-#ifndef _WINDEF_ +- // if these aren't defined already by Windows headers, define now +- +- typedef int BOOL; +- +- #define FALSE 0 +- #define TRUE 1 +- +-#endif // _WINDEF_ ++#include "soundtouch_config.h" + ++#ifdef WIN32 ++#define EXPORT __declspec(dllexport) ++#else ++#define EXPORT ++#endif + + namespace soundtouch + { +@@ -82,7 +74,7 @@ + /// also in GNU environment, then please #undef the INTEGER_SAMPLE + /// and FLOAT_SAMPLE defines first as in comments above. + //#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples +- #define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples ++ #define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples + + #endif + +@@ -144,10 +136,10 @@ + + #endif // SOUNDTOUCH_INTEGER_SAMPLES + +-}; ++} + + // define ST_NO_EXCEPTION_HANDLING switch to disable throwing std exceptions: +-// #define ST_NO_EXCEPTION_HANDLING 1 ++#define ST_NO_EXCEPTION_HANDLING 1 + #ifdef ST_NO_EXCEPTION_HANDLING + // Exceptions disabled. Throw asserts instead if enabled. + #include +--- /src/SoundTouch.h 2012-08-02 10:04:06.301691592 -0700 ++++ /src/SoundTouch.h +@@ -141,7 +141,7 @@ + /// tempo/pitch/rate/samplerate settings. + #define SETTING_NOMINAL_OUTPUT_SEQUENCE 7 + +-class SoundTouch : public FIFOProcessor ++class EXPORT SoundTouch : public FIFOProcessor + { + private: + /// Rate transposer class instance +@@ -160,7 +160,7 @@ + float virtualPitch; + + /// Flag: Has sample rate been set? +- BOOL bSrateSet; ++ bool bSrateSet; + + /// Calculates effective rate & tempo valuescfrom 'virtualRate', 'virtualTempo' and + /// 'virtualPitch' parameters. +@@ -247,8 +247,8 @@ + /// Changes a setting controlling the processing system behaviour. See the + /// 'SETTING_...' defines for available setting ID's. + /// +- /// \return 'TRUE' if the setting was succesfully changed +- BOOL setSetting(int settingId, ///< Setting ID number. see SETTING_... defines. ++ /// \return 'true' if the setting was succesfully changed ++ bool setSetting(int settingId, ///< Setting ID number. see SETTING_... defines. + int value ///< New setting value. + ); + +--- /src/RateTransposer.cpp ++++ /src/RateTransposer.cpp +@@ -120,17 +120,17 @@ RateTransposer *RateTransposer::newInsta + #endif + } + + + // Constructor + RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer) + { + numChannels = 2; +- bUseAAFilter = TRUE; ++ bUseAAFilter = true; + fRate = 0; + + // Instantiates the anti-alias filter with default tap length + // of 32 + pAAFilter = new AAFilter(32); + } + + +@@ -138,24 +138,24 @@ RateTransposer::RateTransposer() : FIFOP + RateTransposer::~RateTransposer() + { + delete pAAFilter; + } + + + + /// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable +-void RateTransposer::enableAAFilter(BOOL newMode) ++void RateTransposer::enableAAFilter(bool newMode) + { + bUseAAFilter = newMode; + } + + + /// Returns nonzero if anti-alias filter is enabled. +-BOOL RateTransposer::isAAFilterEnabled() const ++bool RateTransposer::isAAFilterEnabled() const + { + return bUseAAFilter; + } + + + AAFilter *RateTransposer::getAAFilter() + { + return pAAFilter; +@@ -281,17 +281,17 @@ void RateTransposer::processSamples(cons + uint count; + uint sizeReq; + + if (nSamples == 0) return; + assert(pAAFilter); + + // If anti-alias filter is turned off, simply transpose without applying + // the filter +- if (bUseAAFilter == FALSE) ++ if (bUseAAFilter == false) + { + sizeReq = (uint)((float)nSamples / fRate + 1.0f); + count = transpose(outputBuffer.ptrEnd(sizeReq), src, nSamples); + outputBuffer.putSamples(count); + return; + } + + // Transpose with anti-alias filter +--- /src/RateTransposer.h ++++ /src/RateTransposer.h +@@ -76,17 +76,17 @@ protected: + FIFOSampleBuffer storeBuffer; + + /// Buffer for keeping samples between transposing & anti-alias filter + FIFOSampleBuffer tempBuffer; + + /// Output sample buffer + FIFOSampleBuffer outputBuffer; + +- BOOL bUseAAFilter; ++ bool bUseAAFilter; + + virtual void resetRegisters() = 0; + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) = 0; + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, +@@ -126,20 +126,20 @@ public: + + /// Returns the store buffer object + FIFOSamplePipe *getStore() { return &storeBuffer; }; + + /// Return anti-alias filter object + AAFilter *getAAFilter(); + + /// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable +- void enableAAFilter(BOOL newMode); ++ void enableAAFilter(bool newMode); + + /// Returns nonzero if anti-alias filter is enabled. +- BOOL isAAFilterEnabled() const; ++ bool isAAFilterEnabled() const; + + /// Sets new target rate. Normal rate = 1.0, smaller values represent slower + /// rate, larger faster rates. + virtual void setRate(float newRate); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(int channels); + +--- /src/SoundTouch.cpp ++++ /src/SoundTouch.cpp +@@ -106,17 +106,17 @@ SoundTouch::SoundTouch() + + virtualPitch = + virtualRate = + virtualTempo = 1.0; + + calcEffectiveRateAndTempo(); + + channels = 0; +- bSrateSet = FALSE; ++ bSrateSet = false; + } + + + + SoundTouch::~SoundTouch() + { + delete pRateTransposer; + delete pTDStretch; +@@ -277,27 +277,27 @@ void SoundTouch::calcEffectiveRateAndTem + } + } + } + + + // Sets sample rate. + void SoundTouch::setSampleRate(uint srate) + { +- bSrateSet = TRUE; ++ bSrateSet = true; + // set sample rate, leave other tempo changer parameters as they are. + pTDStretch->setParameters((int)srate); + } + + + // Adds 'numSamples' pcs of samples from the 'samples' memory position into + // the input of the object. + void SoundTouch::putSamples(const SAMPLETYPE *samples, uint nSamples) + { +- if (bSrateSet == FALSE) ++ if (bSrateSet == false) + { + ST_THROW_RT_ERROR("SoundTouch : Sample rate not defined"); + } + else if (channels == 0) + { + ST_THROW_RT_ERROR("SoundTouch : Number of channels not defined"); + } + +@@ -382,57 +382,57 @@ void SoundTouch::flush() + pTDStretch->clearInput(); + // yet leave the 'tempoChanger' output intouched as that's where the + // flushed samples are! + } + + + // Changes a setting controlling the processing system behaviour. See the + // 'SETTING_...' defines for available setting ID's. +-BOOL SoundTouch::setSetting(int settingId, int value) ++bool SoundTouch::setSetting(int settingId, int value) + { + int sampleRate, sequenceMs, seekWindowMs, overlapMs; + + // read current tdstretch routine parameters + pTDStretch->getParameters(&sampleRate, &sequenceMs, &seekWindowMs, &overlapMs); + + switch (settingId) + { + case SETTING_USE_AA_FILTER : + // enables / disabless anti-alias filter +- pRateTransposer->enableAAFilter((value != 0) ? TRUE : FALSE); +- return TRUE; ++ pRateTransposer->enableAAFilter((value != 0) ? true : false); ++ return true; + + case SETTING_AA_FILTER_LENGTH : + // sets anti-alias filter length + pRateTransposer->getAAFilter()->setLength(value); +- return TRUE; ++ return true; + + case SETTING_USE_QUICKSEEK : + // enables / disables tempo routine quick seeking algorithm +- pTDStretch->enableQuickSeek((value != 0) ? TRUE : FALSE); +- return TRUE; ++ pTDStretch->enableQuickSeek((value != 0) ? true : false); ++ return true; + + case SETTING_SEQUENCE_MS: + // change time-stretch sequence duration parameter + pTDStretch->setParameters(sampleRate, value, seekWindowMs, overlapMs); +- return TRUE; ++ return true; + + case SETTING_SEEKWINDOW_MS: + // change time-stretch seek window length parameter + pTDStretch->setParameters(sampleRate, sequenceMs, value, overlapMs); +- return TRUE; ++ return true; + + case SETTING_OVERLAP_MS: + // change time-stretch overlap length parameter + pTDStretch->setParameters(sampleRate, sequenceMs, seekWindowMs, value); +- return TRUE; ++ return true; + + default : +- return FALSE; ++ return false; + } + } + + + // Reads a setting controlling the processing system behaviour. See the + // 'SETTING_...' defines for available setting ID's. + // + // Returns the setting value. +--- /src/TDStretch.cpp ++++ /src/TDStretch.cpp +@@ -81,25 +81,25 @@ static const short _scanOffsets[5][24]={ + * + * Implementation of the class 'TDStretch' + * + *****************************************************************************/ + + + TDStretch::TDStretch() : FIFOProcessor(&outputBuffer) + { +- bQuickSeek = FALSE; ++ bQuickSeek = false; + channels = 2; + + pMidBuffer = NULL; + pMidBufferUnaligned = NULL; + overlapLength = 0; + +- bAutoSeqSetting = TRUE; +- bAutoSeekSetting = TRUE; ++ bAutoSeqSetting = true; ++ bAutoSeekSetting = true; + + // outDebt = 0; + skipFract = 0; + + tempo = 1.0f; + setParameters(44100, DEFAULT_SEQUENCE_MS, DEFAULT_SEEKWINDOW_MS, DEFAULT_OVERLAP_MS); + setTempo(1.0f); + +@@ -129,33 +129,33 @@ void TDStretch::setParameters(int aSampl + { + // accept only positive parameter values - if zero or negative, use old values instead + if (aSampleRate > 0) this->sampleRate = aSampleRate; + if (aOverlapMS > 0) this->overlapMs = aOverlapMS; + + if (aSequenceMS > 0) + { + this->sequenceMs = aSequenceMS; +- bAutoSeqSetting = FALSE; ++ bAutoSeqSetting = false; + } + else if (aSequenceMS == 0) + { + // if zero, use automatic setting +- bAutoSeqSetting = TRUE; ++ bAutoSeqSetting = true; + } + + if (aSeekWindowMS > 0) + { + this->seekWindowMs = aSeekWindowMS; +- bAutoSeekSetting = FALSE; ++ bAutoSeekSetting = false; + } + else if (aSeekWindowMS == 0) + { + // if zero, use automatic setting +- bAutoSeekSetting = TRUE; ++ bAutoSeekSetting = true; + } + + calcSeqParameters(); + + calculateOverlapLength(overlapMs); + + // set tempo to recalculate 'sampleReq' + setTempo(tempo); +@@ -229,24 +229,24 @@ void TDStretch::clear() + outputBuffer.clear(); + clearInput(); + } + + + + // Enables/disables the quick position seeking algorithm. Zero to disable, nonzero + // to enable +-void TDStretch::enableQuickSeek(BOOL enable) ++void TDStretch::enableQuickSeek(bool enable) + { + bQuickSeek = enable; + } + + + // Returns nonzero if the quick seeking algorithm is enabled. +-BOOL TDStretch::isQuickSeekEnabled() const ++bool TDStretch::isQuickSeekEnabled() const + { + return bQuickSeek; + } + + + // Seeks for the optimal overlap-mixing position. + int TDStretch::seekBestOverlapPosition(const SAMPLETYPE *refPos) + { +--- /src/TDStretch.h ++++ /src/TDStretch.h +@@ -120,24 +120,24 @@ protected: + int seekLength; + int seekWindowLength; + int overlapDividerBits; + int slopingDivider; + float nominalSkip; + float skipFract; + FIFOSampleBuffer outputBuffer; + FIFOSampleBuffer inputBuffer; +- BOOL bQuickSeek; ++ bool bQuickSeek; + + int sampleRate; + int sequenceMs; + int seekWindowMs; + int overlapMs; +- BOOL bAutoSeqSetting; +- BOOL bAutoSeekSetting; ++ bool bAutoSeqSetting; ++ bool bAutoSeekSetting; + + void acceptNewOverlapLength(int newOverlapLength); + + virtual void clearCrossCorrState(); + void calculateOverlapLength(int overlapMs); + + virtual double calcCrossCorr(const SAMPLETYPE *mixingPos, const SAMPLETYPE *compare) const; + +@@ -188,20 +188,20 @@ public: + /// Clears the input buffer + void clearInput(); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(int numChannels); + + /// Enables/disables the quick position seeking algorithm. Zero to disable, + /// nonzero to enable +- void enableQuickSeek(BOOL enable); ++ void enableQuickSeek(bool enable); + + /// Returns nonzero if the quick seeking algorithm is enabled. +- BOOL isQuickSeekEnabled() const; ++ bool isQuickSeekEnabled() const; + + /// Sets routine control parameters. These control are certain time constants + /// defining how the sound is stretched to the desired duration. + // + /// 'sampleRate' = sample rate of the sound + /// 'sequenceMS' = one processing sequence length in milliseconds + /// 'seekwindowMS' = seeking window length for scanning the best overlapping + /// position +only in patch2: +--- /src/cpu_detect_x86.cpp 2012-04-12 19:52:12.743376976 -0700 ++++ /src/cpu_detect_x86.cpp 2012-08-02 09:54:24.561712171 -0700 +@@ -38,30 +38,35 @@ + // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + // + //////////////////////////////////////////////////////////////////////////////// + + #include "cpu_detect.h" + #include "STTypes.h" + + #if defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) +- +- #if defined(__GNUC__) && defined(__i386__) +- // gcc ++ #if defined(__GNUC__) ++ // gcc and clang + #include "cpuid.h" + #endif + + #if defined(_M_IX86) + // windows + #include +- #define bit_MMX (1 << 23) +- #define bit_SSE (1 << 25) +- #define bit_SSE2 (1 << 26) + #endif +- ++ // If we still don't have the macros, define them (Windows, MacOS) ++ #ifndef bit_MMX ++ #define bit_MMX (1 << 23) ++ #endif ++ #ifndef bit_SSE ++ #define bit_SSE (1 << 25) ++ #endif ++ #ifndef bit_SSE2 ++ #define bit_SSE2 (1 << 26) ++ #endif + #endif + + + ////////////////////////////////////////////////////////////////////////////// + // + // processor instructions extension detection routines + // + ////////////////////////////////////////////////////////////////////////////// diff --git a/media/libsoundtouch/src/AAFilter.cpp b/media/libsoundtouch/src/AAFilter.cpp new file mode 100644 index 00000000000..29de4096b4b --- /dev/null +++ b/media/libsoundtouch/src/AAFilter.cpp @@ -0,0 +1,184 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// FIR low-pass (anti-alias) filter with filter coefficient design routine and +/// MMX optimization. +/// +/// Anti-alias filter is used to prevent folding of high frequencies when +/// transposing the sample rate with interpolation. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2009-01-11 03:34:24 -0800 (Sun, 11 Jan 2009) $ +// File revision : $Revision: 4 $ +// +// $Id: AAFilter.cpp 45 2009-01-11 11:34:24Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "AAFilter.h" +#include "FIRFilter.h" + +using namespace soundtouch; + +#define PI 3.141592655357989 +#define TWOPI (2 * PI) + +/***************************************************************************** + * + * Implementation of the class 'AAFilter' + * + *****************************************************************************/ + +AAFilter::AAFilter(uint len) +{ + pFIR = FIRFilter::newInstance(); + cutoffFreq = 0.5; + setLength(len); +} + + + +AAFilter::~AAFilter() +{ + delete pFIR; +} + + + +// Sets new anti-alias filter cut-off edge frequency, scaled to +// sampling frequency (nyquist frequency = 0.5). +// The filter will cut frequencies higher than the given frequency. +void AAFilter::setCutoffFreq(double newCutoffFreq) +{ + cutoffFreq = newCutoffFreq; + calculateCoeffs(); +} + + + +// Sets number of FIR filter taps +void AAFilter::setLength(uint newLength) +{ + length = newLength; + calculateCoeffs(); +} + + + +// Calculates coefficients for a low-pass FIR filter using Hamming window +void AAFilter::calculateCoeffs() +{ + uint i; + double cntTemp, temp, tempCoeff,h, w; + double fc2, wc; + double scaleCoeff, sum; + double *work; + SAMPLETYPE *coeffs; + + assert(length >= 2); + assert(length % 4 == 0); + assert(cutoffFreq >= 0); + assert(cutoffFreq <= 0.5); + + work = new double[length]; + coeffs = new SAMPLETYPE[length]; + + fc2 = 2.0 * cutoffFreq; + wc = PI * fc2; + tempCoeff = TWOPI / (double)length; + + sum = 0; + for (i = 0; i < length; i ++) + { + cntTemp = (double)i - (double)(length / 2); + + temp = cntTemp * wc; + if (temp != 0) + { + h = fc2 * sin(temp) / temp; // sinc function + } + else + { + h = 1.0; + } + w = 0.54 + 0.46 * cos(tempCoeff * cntTemp); // hamming window + + temp = w * h; + work[i] = temp; + + // calc net sum of coefficients + sum += temp; + } + + // ensure the sum of coefficients is larger than zero + assert(sum > 0); + + // ensure we've really designed a lowpass filter... + assert(work[length/2] > 0); + assert(work[length/2 + 1] > -1e-6); + assert(work[length/2 - 1] > -1e-6); + + // Calculate a scaling coefficient in such a way that the result can be + // divided by 16384 + scaleCoeff = 16384.0f / sum; + + for (i = 0; i < length; i ++) + { + // scale & round to nearest integer + temp = work[i] * scaleCoeff; + temp += (temp >= 0) ? 0.5 : -0.5; + // ensure no overfloods + assert(temp >= -32768 && temp <= 32767); + coeffs[i] = (SAMPLETYPE)temp; + } + + // Set coefficients. Use divide factor 14 => divide result by 2^14 = 16384 + pFIR->setCoefficients(coeffs, length, 14); + + delete[] work; + delete[] coeffs; +} + + +// Applies the filter to the given sequence of samples. +// Note : The amount of outputted samples is by value of 'filter length' +// smaller than the amount of input samples. +uint AAFilter::evaluate(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) const +{ + return pFIR->evaluate(dest, src, numSamples, numChannels); +} + + +uint AAFilter::getLength() const +{ + return pFIR->getLength(); +} diff --git a/media/libsoundtouch/src/AAFilter.h b/media/libsoundtouch/src/AAFilter.h new file mode 100644 index 00000000000..7f172842e84 --- /dev/null +++ b/media/libsoundtouch/src/AAFilter.h @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo +/// while maintaining the original pitch by using a time domain WSOLA-like method +/// with several performance-increasing tweaks. +/// +/// Anti-alias filter is used to prevent folding of high frequencies when +/// transposing the sample rate with interpolation. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2008-02-10 08:26:55 -0800 (Sun, 10 Feb 2008) $ +// File revision : $Revision: 4 $ +// +// $Id: AAFilter.h 11 2008-02-10 16:26:55Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef AAFilter_H +#define AAFilter_H + +#include "STTypes.h" + +namespace soundtouch +{ + +class AAFilter +{ +protected: + class FIRFilter *pFIR; + + /// Low-pass filter cut-off frequency, negative = invalid + double cutoffFreq; + + /// num of filter taps + uint length; + + /// Calculate the FIR coefficients realizing the given cutoff-frequency + void calculateCoeffs(); +public: + AAFilter(uint length); + + ~AAFilter(); + + /// Sets new anti-alias filter cut-off edge frequency, scaled to sampling + /// frequency (nyquist frequency = 0.5). The filter will cut off the + /// frequencies than that. + void setCutoffFreq(double newCutoffFreq); + + /// Sets number of FIR filter taps, i.e. ~filter complexity + void setLength(uint newLength); + + uint getLength() const; + + /// Applies the filter to the given sequence of samples. + /// Note : The amount of outputted samples is by value of 'filter length' + /// smaller than the amount of input samples. + uint evaluate(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples, + uint numChannels) const; +}; + +} + +#endif diff --git a/media/libsoundtouch/src/FIFOSampleBuffer.cpp b/media/libsoundtouch/src/FIFOSampleBuffer.cpp new file mode 100644 index 00000000000..bda169276fb --- /dev/null +++ b/media/libsoundtouch/src/FIFOSampleBuffer.cpp @@ -0,0 +1,274 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// A buffer class for temporarily storaging sound samples, operates as a +/// first-in-first-out pipe. +/// +/// Samples are added to the end of the sample buffer with the 'putSamples' +/// function, and are received from the beginning of the buffer by calling +/// the 'receiveSamples' function. The class automatically removes the +/// outputted samples from the buffer, as well as grows the buffer size +/// whenever necessary. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: FIFOSampleBuffer.cpp 143 2012-06-13 19:29:53Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "FIFOSampleBuffer.h" + +using namespace soundtouch; + +// Constructor +FIFOSampleBuffer::FIFOSampleBuffer(int numChannels) +{ + assert(numChannels > 0); + sizeInBytes = 0; // reasonable initial value + buffer = NULL; + bufferUnaligned = NULL; + samplesInBuffer = 0; + bufferPos = 0; + channels = (uint)numChannels; + ensureCapacity(32); // allocate initial capacity +} + + +// destructor +FIFOSampleBuffer::~FIFOSampleBuffer() +{ + delete[] bufferUnaligned; + bufferUnaligned = NULL; + buffer = NULL; +} + + +// Sets number of channels, 1 = mono, 2 = stereo +void FIFOSampleBuffer::setChannels(int numChannels) +{ + uint usedBytes; + + assert(numChannels > 0); + usedBytes = channels * samplesInBuffer; + channels = (uint)numChannels; + samplesInBuffer = usedBytes / channels; +} + + +// if output location pointer 'bufferPos' isn't zero, 'rewinds' the buffer and +// zeroes this pointer by copying samples from the 'bufferPos' pointer +// location on to the beginning of the buffer. +void FIFOSampleBuffer::rewind() +{ + if (buffer && bufferPos) + { + memmove(buffer, ptrBegin(), sizeof(SAMPLETYPE) * channels * samplesInBuffer); + bufferPos = 0; + } +} + + +// Adds 'numSamples' pcs of samples from the 'samples' memory position to +// the sample buffer. +void FIFOSampleBuffer::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + memcpy(ptrEnd(nSamples), samples, sizeof(SAMPLETYPE) * nSamples * channels); + samplesInBuffer += nSamples; +} + + +// Increases the number of samples in the buffer without copying any actual +// samples. +// +// This function is used to update the number of samples in the sample buffer +// when accessing the buffer directly with 'ptrEnd' function. Please be +// careful though! +void FIFOSampleBuffer::putSamples(uint nSamples) +{ + uint req; + + req = samplesInBuffer + nSamples; + ensureCapacity(req); + samplesInBuffer += nSamples; +} + + +// Returns a pointer to the end of the used part of the sample buffer (i.e. +// where the new samples are to be inserted). This function may be used for +// inserting new samples into the sample buffer directly. Please be careful! +// +// Parameter 'slackCapacity' tells the function how much free capacity (in +// terms of samples) there _at least_ should be, in order to the caller to +// succesfully insert all the required samples to the buffer. When necessary, +// the function grows the buffer size to comply with this requirement. +// +// When using this function as means for inserting new samples, also remember +// to increase the sample count afterwards, by calling the +// 'putSamples(numSamples)' function. +SAMPLETYPE *FIFOSampleBuffer::ptrEnd(uint slackCapacity) +{ + ensureCapacity(samplesInBuffer + slackCapacity); + return buffer + samplesInBuffer * channels; +} + + +// Returns a pointer to the beginning of the currently non-outputted samples. +// This function is provided for accessing the output samples directly. +// Please be careful! +// +// When using this function to output samples, also remember to 'remove' the +// outputted samples from the buffer by calling the +// 'receiveSamples(numSamples)' function +SAMPLETYPE *FIFOSampleBuffer::ptrBegin() +{ + assert(buffer); + return buffer + bufferPos * channels; +} + + +// Ensures that the buffer has enought capacity, i.e. space for _at least_ +// 'capacityRequirement' number of samples. The buffer is grown in steps of +// 4 kilobytes to eliminate the need for frequently growing up the buffer, +// as well as to round the buffer size up to the virtual memory page size. +void FIFOSampleBuffer::ensureCapacity(uint capacityRequirement) +{ + SAMPLETYPE *tempUnaligned, *temp; + + if (capacityRequirement > getCapacity()) + { + // enlarge the buffer in 4kbyte steps (round up to next 4k boundary) + sizeInBytes = (capacityRequirement * channels * sizeof(SAMPLETYPE) + 4095) & (uint)-4096; + assert(sizeInBytes % 2 == 0); + tempUnaligned = new SAMPLETYPE[sizeInBytes / sizeof(SAMPLETYPE) + 16 / sizeof(SAMPLETYPE)]; + if (tempUnaligned == NULL) + { + ST_THROW_RT_ERROR("Couldn't allocate memory!\n"); + } + // Align the buffer to begin at 16byte cache line boundary for optimal performance + temp = (SAMPLETYPE *)(((ulong)tempUnaligned + 15) & (ulong)-16); + if (samplesInBuffer) + { + memcpy(temp, ptrBegin(), samplesInBuffer * channels * sizeof(SAMPLETYPE)); + } + delete[] bufferUnaligned; + buffer = temp; + bufferUnaligned = tempUnaligned; + bufferPos = 0; + } + else + { + // simply rewind the buffer (if necessary) + rewind(); + } +} + + +// Returns the current buffer capacity in terms of samples +uint FIFOSampleBuffer::getCapacity() const +{ + return sizeInBytes / (channels * sizeof(SAMPLETYPE)); +} + + +// Returns the number of samples currently in the buffer +uint FIFOSampleBuffer::numSamples() const +{ + return samplesInBuffer; +} + + +// Output samples from beginning of the sample buffer. Copies demanded number +// of samples to output and removes them from the sample buffer. If there +// are less than 'numsample' samples in the buffer, returns all available. +// +// Returns number of samples copied. +uint FIFOSampleBuffer::receiveSamples(SAMPLETYPE *output, uint maxSamples) +{ + uint num; + + num = (maxSamples > samplesInBuffer) ? samplesInBuffer : maxSamples; + + memcpy(output, ptrBegin(), channels * sizeof(SAMPLETYPE) * num); + return receiveSamples(num); +} + + +// Removes samples from the beginning of the sample buffer without copying them +// anywhere. Used to reduce the number of samples in the buffer, when accessing +// the sample buffer with the 'ptrBegin' function. +uint FIFOSampleBuffer::receiveSamples(uint maxSamples) +{ + if (maxSamples >= samplesInBuffer) + { + uint temp; + + temp = samplesInBuffer; + samplesInBuffer = 0; + return temp; + } + + samplesInBuffer -= maxSamples; + bufferPos += maxSamples; + + return maxSamples; +} + + +// Returns nonzero if the sample buffer is empty +int FIFOSampleBuffer::isEmpty() const +{ + return (samplesInBuffer == 0) ? 1 : 0; +} + + +// Clears the sample buffer +void FIFOSampleBuffer::clear() +{ + samplesInBuffer = 0; + bufferPos = 0; +} + + +/// allow trimming (downwards) amount of samples in pipeline. +/// Returns adjusted amount of samples +uint FIFOSampleBuffer::adjustAmountOfSamples(uint numSamples) +{ + if (numSamples < samplesInBuffer) + { + samplesInBuffer = numSamples; + } + return samplesInBuffer; +} + diff --git a/media/libsoundtouch/src/FIFOSampleBuffer.h b/media/libsoundtouch/src/FIFOSampleBuffer.h new file mode 100644 index 00000000000..34702d94c76 --- /dev/null +++ b/media/libsoundtouch/src/FIFOSampleBuffer.h @@ -0,0 +1,178 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// A buffer class for temporarily storaging sound samples, operates as a +/// first-in-first-out pipe. +/// +/// Samples are added to the end of the sample buffer with the 'putSamples' +/// function, and are received from the beginning of the buffer by calling +/// the 'receiveSamples' function. The class automatically removes the +/// output samples from the buffer as well as grows the storage size +/// whenever necessary. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: FIFOSampleBuffer.h 143 2012-06-13 19:29:53Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FIFOSampleBuffer_H +#define FIFOSampleBuffer_H + +#include "FIFOSamplePipe.h" + +namespace soundtouch +{ + +/// Sample buffer working in FIFO (first-in-first-out) principle. The class takes +/// care of storage size adjustment and data moving during input/output operations. +/// +/// Notice that in case of stereo audio, one sample is considered to consist of +/// both channel data. +class FIFOSampleBuffer : public FIFOSamplePipe +{ +private: + /// Sample buffer. + SAMPLETYPE *buffer; + + // Raw unaligned buffer memory. 'buffer' is made aligned by pointing it to first + // 16-byte aligned location of this buffer + SAMPLETYPE *bufferUnaligned; + + /// Sample buffer size in bytes + uint sizeInBytes; + + /// How many samples are currently in buffer. + uint samplesInBuffer; + + /// Channels, 1=mono, 2=stereo. + uint channels; + + /// Current position pointer to the buffer. This pointer is increased when samples are + /// removed from the pipe so that it's necessary to actually rewind buffer (move data) + /// only new data when is put to the pipe. + uint bufferPos; + + /// Rewind the buffer by moving data from position pointed by 'bufferPos' to real + /// beginning of the buffer. + void rewind(); + + /// Ensures that the buffer has capacity for at least this many samples. + void ensureCapacity(uint capacityRequirement); + + /// Returns current capacity. + uint getCapacity() const; + +public: + + /// Constructor + FIFOSampleBuffer(int numChannels = 2 ///< Number of channels, 1=mono, 2=stereo. + ///< Default is stereo. + ); + + /// destructor + ~FIFOSampleBuffer(); + + /// Returns a pointer to the beginning of the output samples. + /// This function is provided for accessing the output samples directly. + /// Please be careful for not to corrupt the book-keeping! + /// + /// When using this function to output samples, also remember to 'remove' the + /// output samples from the buffer by calling the + /// 'receiveSamples(numSamples)' function + virtual SAMPLETYPE *ptrBegin(); + + /// Returns a pointer to the end of the used part of the sample buffer (i.e. + /// where the new samples are to be inserted). This function may be used for + /// inserting new samples into the sample buffer directly. Please be careful + /// not corrupt the book-keeping! + /// + /// When using this function as means for inserting new samples, also remember + /// to increase the sample count afterwards, by calling the + /// 'putSamples(numSamples)' function. + SAMPLETYPE *ptrEnd( + uint slackCapacity ///< How much free capacity (in samples) there _at least_ + ///< should be so that the caller can succesfully insert the + ///< desired samples to the buffer. If necessary, the function + ///< grows the buffer size to comply with this requirement. + ); + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position to + /// the sample buffer. + virtual void putSamples(const SAMPLETYPE *samples, ///< Pointer to samples. + uint numSamples ///< Number of samples to insert. + ); + + /// Adjusts the book-keeping to increase number of samples in the buffer without + /// copying any actual samples. + /// + /// This function is used to update the number of samples in the sample buffer + /// when accessing the buffer directly with 'ptrEnd' function. Please be + /// careful though! + virtual void putSamples(uint numSamples ///< Number of samples been inserted. + ); + + /// Output samples from beginning of the sample buffer. Copies requested samples to + /// output buffer and removes them from the sample buffer. If there are less than + /// 'numsample' samples in the buffer, returns all that available. + /// + /// \return Number of samples returned. + virtual uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples. + uint maxSamples ///< How many samples to receive at max. + ); + + /// Adjusts book-keeping so that given number of samples are removed from beginning of the + /// sample buffer without copying them anywhere. + /// + /// Used to reduce the number of samples in the buffer when accessing the sample buffer directly + /// with 'ptrBegin' function. + virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe. + ); + + /// Returns number of samples currently available. + virtual uint numSamples() const; + + /// Sets number of channels, 1 = mono, 2 = stereo. + void setChannels(int numChannels); + + /// Returns nonzero if there aren't any samples available for outputting. + virtual int isEmpty() const; + + /// Clears all the samples. + virtual void clear(); + + /// allow trimming (downwards) amount of samples in pipeline. + /// Returns adjusted amount of samples + uint adjustAmountOfSamples(uint numSamples); +}; + +} + +#endif diff --git a/media/libsoundtouch/src/FIFOSamplePipe.h b/media/libsoundtouch/src/FIFOSamplePipe.h new file mode 100644 index 00000000000..dde528c8664 --- /dev/null +++ b/media/libsoundtouch/src/FIFOSamplePipe.h @@ -0,0 +1,234 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// 'FIFOSamplePipe' : An abstract base class for classes that manipulate sound +/// samples by operating like a first-in-first-out pipe: New samples are fed +/// into one end of the pipe with the 'putSamples' function, and the processed +/// samples are received from the other end with the 'receiveSamples' function. +/// +/// 'FIFOProcessor' : A base class for classes the do signal processing with +/// the samples while operating like a first-in-first-out pipe. When samples +/// are input with the 'putSamples' function, the class processes them +/// and moves the processed samples to the given 'output' pipe object, which +/// may be either another processing stage, or a fifo sample buffer object. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: FIFOSamplePipe.h 143 2012-06-13 19:29:53Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FIFOSamplePipe_H +#define FIFOSamplePipe_H + +#include +#include +#include "STTypes.h" + +namespace soundtouch +{ + +/// Abstract base class for FIFO (first-in-first-out) sample processing classes. +class FIFOSamplePipe +{ +public: + // virtual default destructor + virtual ~FIFOSamplePipe() {} + + + /// Returns a pointer to the beginning of the output samples. + /// This function is provided for accessing the output samples directly. + /// Please be careful for not to corrupt the book-keeping! + /// + /// When using this function to output samples, also remember to 'remove' the + /// output samples from the buffer by calling the + /// 'receiveSamples(numSamples)' function + virtual SAMPLETYPE *ptrBegin() = 0; + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position to + /// the sample buffer. + virtual void putSamples(const SAMPLETYPE *samples, ///< Pointer to samples. + uint numSamples ///< Number of samples to insert. + ) = 0; + + + // Moves samples from the 'other' pipe instance to this instance. + void moveSamples(FIFOSamplePipe &other ///< Other pipe instance where from the receive the data. + ) + { + int oNumSamples = other.numSamples(); + + putSamples(other.ptrBegin(), oNumSamples); + other.receiveSamples(oNumSamples); + }; + + /// Output samples from beginning of the sample buffer. Copies requested samples to + /// output buffer and removes them from the sample buffer. If there are less than + /// 'numsample' samples in the buffer, returns all that available. + /// + /// \return Number of samples returned. + virtual uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples. + uint maxSamples ///< How many samples to receive at max. + ) = 0; + + /// Adjusts book-keeping so that given number of samples are removed from beginning of the + /// sample buffer without copying them anywhere. + /// + /// Used to reduce the number of samples in the buffer when accessing the sample buffer directly + /// with 'ptrBegin' function. + virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe. + ) = 0; + + /// Returns number of samples currently available. + virtual uint numSamples() const = 0; + + // Returns nonzero if there aren't any samples available for outputting. + virtual int isEmpty() const = 0; + + /// Clears all the samples. + virtual void clear() = 0; + + /// allow trimming (downwards) amount of samples in pipeline. + /// Returns adjusted amount of samples + virtual uint adjustAmountOfSamples(uint numSamples) = 0; + +}; + + + +/// Base-class for sound processing routines working in FIFO principle. With this base +/// class it's easy to implement sound processing stages that can be chained together, +/// so that samples that are fed into beginning of the pipe automatically go through +/// all the processing stages. +/// +/// When samples are input to this class, they're first processed and then put to +/// the FIFO pipe that's defined as output of this class. This output pipe can be +/// either other processing stage or a FIFO sample buffer. +class FIFOProcessor :public FIFOSamplePipe +{ +protected: + /// Internal pipe where processed samples are put. + FIFOSamplePipe *output; + + /// Sets output pipe. + void setOutPipe(FIFOSamplePipe *pOutput) + { + assert(output == NULL); + assert(pOutput != NULL); + output = pOutput; + } + + + /// Constructor. Doesn't define output pipe; it has to be set be + /// 'setOutPipe' function. + FIFOProcessor() + { + output = NULL; + } + + + /// Constructor. Configures output pipe. + FIFOProcessor(FIFOSamplePipe *pOutput ///< Output pipe. + ) + { + output = pOutput; + } + + + /// Destructor. + virtual ~FIFOProcessor() + { + } + + + /// Returns a pointer to the beginning of the output samples. + /// This function is provided for accessing the output samples directly. + /// Please be careful for not to corrupt the book-keeping! + /// + /// When using this function to output samples, also remember to 'remove' the + /// output samples from the buffer by calling the + /// 'receiveSamples(numSamples)' function + virtual SAMPLETYPE *ptrBegin() + { + return output->ptrBegin(); + } + +public: + + /// Output samples from beginning of the sample buffer. Copies requested samples to + /// output buffer and removes them from the sample buffer. If there are less than + /// 'numsample' samples in the buffer, returns all that available. + /// + /// \return Number of samples returned. + virtual uint receiveSamples(SAMPLETYPE *outBuffer, ///< Buffer where to copy output samples. + uint maxSamples ///< How many samples to receive at max. + ) + { + return output->receiveSamples(outBuffer, maxSamples); + } + + + /// Adjusts book-keeping so that given number of samples are removed from beginning of the + /// sample buffer without copying them anywhere. + /// + /// Used to reduce the number of samples in the buffer when accessing the sample buffer directly + /// with 'ptrBegin' function. + virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe. + ) + { + return output->receiveSamples(maxSamples); + } + + + /// Returns number of samples currently available. + virtual uint numSamples() const + { + return output->numSamples(); + } + + + /// Returns nonzero if there aren't any samples available for outputting. + virtual int isEmpty() const + { + return output->isEmpty(); + } + + /// allow trimming (downwards) amount of samples in pipeline. + /// Returns adjusted amount of samples + virtual uint adjustAmountOfSamples(uint numSamples) + { + return output->adjustAmountOfSamples(numSamples); + } + +}; + +} + +#endif diff --git a/media/libsoundtouch/src/FIRFilter.cpp b/media/libsoundtouch/src/FIRFilter.cpp new file mode 100644 index 00000000000..3ba568f642f --- /dev/null +++ b/media/libsoundtouch/src/FIRFilter.cpp @@ -0,0 +1,259 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// General FIR digital filter routines with MMX optimization. +/// +/// Note : MMX optimized functions reside in a separate, platform-specific file, +/// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2011-09-02 11:56:11 -0700 (Fri, 02 Sep 2011) $ +// File revision : $Revision: 4 $ +// +// $Id: FIRFilter.cpp 131 2011-09-02 18:56:11Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "FIRFilter.h" +#include "cpu_detect.h" + +using namespace soundtouch; + +/***************************************************************************** + * + * Implementation of the class 'FIRFilter' + * + *****************************************************************************/ + +FIRFilter::FIRFilter() +{ + resultDivFactor = 0; + resultDivider = 0; + length = 0; + lengthDiv8 = 0; + filterCoeffs = NULL; +} + + +FIRFilter::~FIRFilter() +{ + delete[] filterCoeffs; +} + +// Usual C-version of the filter routine for stereo sound +uint FIRFilter::evaluateFilterStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples) const +{ + uint i, j, end; + LONG_SAMPLETYPE suml, sumr; +#ifdef SOUNDTOUCH_FLOAT_SAMPLES + // when using floating point samples, use a scaler instead of a divider + // because division is much slower operation than multiplying. + double dScaler = 1.0 / (double)resultDivider; +#endif + + assert(length != 0); + assert(src != NULL); + assert(dest != NULL); + assert(filterCoeffs != NULL); + + end = 2 * (numSamples - length); + + for (j = 0; j < end; j += 2) + { + const SAMPLETYPE *ptr; + + suml = sumr = 0; + ptr = src + j; + + for (i = 0; i < length; i += 4) + { + // loop is unrolled by factor of 4 here for efficiency + suml += ptr[2 * i + 0] * filterCoeffs[i + 0] + + ptr[2 * i + 2] * filterCoeffs[i + 1] + + ptr[2 * i + 4] * filterCoeffs[i + 2] + + ptr[2 * i + 6] * filterCoeffs[i + 3]; + sumr += ptr[2 * i + 1] * filterCoeffs[i + 0] + + ptr[2 * i + 3] * filterCoeffs[i + 1] + + ptr[2 * i + 5] * filterCoeffs[i + 2] + + ptr[2 * i + 7] * filterCoeffs[i + 3]; + } + +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + suml >>= resultDivFactor; + sumr >>= resultDivFactor; + // saturate to 16 bit integer limits + suml = (suml < -32768) ? -32768 : (suml > 32767) ? 32767 : suml; + // saturate to 16 bit integer limits + sumr = (sumr < -32768) ? -32768 : (sumr > 32767) ? 32767 : sumr; +#else + suml *= dScaler; + sumr *= dScaler; +#endif // SOUNDTOUCH_INTEGER_SAMPLES + dest[j] = (SAMPLETYPE)suml; + dest[j + 1] = (SAMPLETYPE)sumr; + } + return numSamples - length; +} + + + + +// Usual C-version of the filter routine for mono sound +uint FIRFilter::evaluateFilterMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples) const +{ + uint i, j, end; + LONG_SAMPLETYPE sum; +#ifdef SOUNDTOUCH_FLOAT_SAMPLES + // when using floating point samples, use a scaler instead of a divider + // because division is much slower operation than multiplying. + double dScaler = 1.0 / (double)resultDivider; +#endif + + + assert(length != 0); + + end = numSamples - length; + for (j = 0; j < end; j ++) + { + sum = 0; + for (i = 0; i < length; i += 4) + { + // loop is unrolled by factor of 4 here for efficiency + sum += src[i + 0] * filterCoeffs[i + 0] + + src[i + 1] * filterCoeffs[i + 1] + + src[i + 2] * filterCoeffs[i + 2] + + src[i + 3] * filterCoeffs[i + 3]; + } +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + sum >>= resultDivFactor; + // saturate to 16 bit integer limits + sum = (sum < -32768) ? -32768 : (sum > 32767) ? 32767 : sum; +#else + sum *= dScaler; +#endif // SOUNDTOUCH_INTEGER_SAMPLES + dest[j] = (SAMPLETYPE)sum; + src ++; + } + return end; +} + + +// Set filter coeffiecients and length. +// +// Throws an exception if filter length isn't divisible by 8 +void FIRFilter::setCoefficients(const SAMPLETYPE *coeffs, uint newLength, uint uResultDivFactor) +{ + assert(newLength > 0); + if (newLength % 8) ST_THROW_RT_ERROR("FIR filter length not divisible by 8"); + + lengthDiv8 = newLength / 8; + length = lengthDiv8 * 8; + assert(length == newLength); + + resultDivFactor = uResultDivFactor; + resultDivider = (SAMPLETYPE)::pow(2.0, (int)resultDivFactor); + + delete[] filterCoeffs; + filterCoeffs = new SAMPLETYPE[length]; + memcpy(filterCoeffs, coeffs, length * sizeof(SAMPLETYPE)); +} + + +uint FIRFilter::getLength() const +{ + return length; +} + + + +// Applies the filter to the given sequence of samples. +// +// Note : The amount of outputted samples is by value of 'filter_length' +// smaller than the amount of input samples. +uint FIRFilter::evaluate(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) const +{ + assert(numChannels == 1 || numChannels == 2); + + assert(length > 0); + assert(lengthDiv8 * 8 == length); + if (numSamples < length) return 0; + if (numChannels == 2) + { + return evaluateFilterStereo(dest, src, numSamples); + } else { + return evaluateFilterMono(dest, src, numSamples); + } +} + + + +// Operator 'new' is overloaded so that it automatically creates a suitable instance +// depending on if we've a MMX-capable CPU available or not. +void * FIRFilter::operator new(size_t s) +{ + // Notice! don't use "new FIRFilter" directly, use "newInstance" to create a new instance instead! + ST_THROW_RT_ERROR("Error in FIRFilter::new: Don't use 'new FIRFilter', use 'newInstance' member instead!"); + return newInstance(); +} + + +FIRFilter * FIRFilter::newInstance() +{ + uint uExtensions; + + uExtensions = detectCPUextensions(); + + // Check if MMX/SSE instruction set extensions supported by CPU + +#ifdef SOUNDTOUCH_ALLOW_MMX + // MMX routines available only with integer sample types + if (uExtensions & SUPPORT_MMX) + { + return ::new FIRFilterMMX; + } + else +#endif // SOUNDTOUCH_ALLOW_MMX + +#ifdef SOUNDTOUCH_ALLOW_SSE + if (uExtensions & SUPPORT_SSE) + { + // SSE support + return ::new FIRFilterSSE; + } + else +#endif // SOUNDTOUCH_ALLOW_SSE + + { + // ISA optimizations not supported, use plain C version + return ::new FIRFilter; + } +} diff --git a/media/libsoundtouch/src/FIRFilter.h b/media/libsoundtouch/src/FIRFilter.h new file mode 100644 index 00000000000..25ede16a8aa --- /dev/null +++ b/media/libsoundtouch/src/FIRFilter.h @@ -0,0 +1,145 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// General FIR digital filter routines with MMX optimization. +/// +/// Note : MMX optimized functions reside in a separate, platform-specific file, +/// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2011-02-13 11:13:57 -0800 (Sun, 13 Feb 2011) $ +// File revision : $Revision: 4 $ +// +// $Id: FIRFilter.h 104 2011-02-13 19:13:57Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FIRFilter_H +#define FIRFilter_H + +#include +#include "STTypes.h" + +namespace soundtouch +{ + +class FIRFilter +{ +protected: + // Number of FIR filter taps + uint length; + // Number of FIR filter taps divided by 8 + uint lengthDiv8; + + // Result divider factor in 2^k format + uint resultDivFactor; + + // Result divider value. + SAMPLETYPE resultDivider; + + // Memory for filter coefficients + SAMPLETYPE *filterCoeffs; + + virtual uint evaluateFilterStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) const; + virtual uint evaluateFilterMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) const; + +public: + FIRFilter(); + virtual ~FIRFilter(); + + /// Operator 'new' is overloaded so that it automatically creates a suitable instance + /// depending on if we've a MMX-capable CPU available or not. + static void * operator new(size_t s); + + static FIRFilter *newInstance(); + + /// Applies the filter to the given sequence of samples. + /// Note : The amount of outputted samples is by value of 'filter_length' + /// smaller than the amount of input samples. + /// + /// \return Number of samples copied to 'dest'. + uint evaluate(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples, + uint numChannels) const; + + uint getLength() const; + + virtual void setCoefficients(const SAMPLETYPE *coeffs, + uint newLength, + uint uResultDivFactor); +}; + + +// Optional subclasses that implement CPU-specific optimizations: + +#ifdef SOUNDTOUCH_ALLOW_MMX + +/// Class that implements MMX optimized functions exclusive for 16bit integer samples type. + class FIRFilterMMX : public FIRFilter + { + protected: + short *filterCoeffsUnalign; + short *filterCoeffsAlign; + + virtual uint evaluateFilterStereo(short *dest, const short *src, uint numSamples) const; + public: + FIRFilterMMX(); + ~FIRFilterMMX(); + + virtual void setCoefficients(const short *coeffs, uint newLength, uint uResultDivFactor); + }; + +#endif // SOUNDTOUCH_ALLOW_MMX + + +#ifdef SOUNDTOUCH_ALLOW_SSE + /// Class that implements SSE optimized functions exclusive for floating point samples type. + class FIRFilterSSE : public FIRFilter + { + protected: + float *filterCoeffsUnalign; + float *filterCoeffsAlign; + + virtual uint evaluateFilterStereo(float *dest, const float *src, uint numSamples) const; + public: + FIRFilterSSE(); + ~FIRFilterSSE(); + + virtual void setCoefficients(const float *coeffs, uint newLength, uint uResultDivFactor); + }; + +#endif // SOUNDTOUCH_ALLOW_SSE + +} + +#endif // FIRFilter_H diff --git a/media/libsoundtouch/src/Makefile.in b/media/libsoundtouch/src/Makefile.in new file mode 100644 index 00000000000..965d04318f8 --- /dev/null +++ b/media/libsoundtouch/src/Makefile.in @@ -0,0 +1,69 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = soundtouch +LIBRARY_NAME = soundtouch +SHORT_LIBNAME = soundt +FORCE_SHARED_LIB = 1 +VISIBILITY_FLAGS = +EXPORTS_NAMESPACES = soundtouch + +ifeq ($(OS_ARCH),WINNT) +ifndef GNU_CC +RCFILE = $(srcdir)/soundtouch.rc +RESFILE = $(srcdir)/soundtouch.res +endif +endif + + +EXTRA_DSO_LDOPTS += $(MOZALLOC_LIB) + +# Use abort() instead of exception in SoundTouch. +DEFINES += -DST_NO_EXCEPTION_HANDLING=1 + +EXPORTS_soundtouch = SoundTouch.h \ + STTypes.h \ + FIFOSamplePipe.h \ + soundtouch_config.h \ + $(NULL) + +CPPSRCS = AAFilter.cpp \ + cpu_detect_x86.cpp \ + FIFOSampleBuffer.cpp \ + FIRFilter.cpp \ + RateTransposer.cpp \ + SoundTouch.cpp \ + TDStretch.cpp \ + $(NULL) + +ifneq (,$(INTEL_ARCHITECTURE)) +ifdef MOZ_SAMPLE_TYPE_FLOAT32 +CPPSRCS += sse_optimized.cpp +else +CPPSRCS += mmx_optimized.cpp +endif +endif + +SOUNDTOUCH_LIBS = $(DLL_PREFIX)soundtouch$(DLL_SUFFIX) + +include $(topsrcdir)/config/rules.mk + +ifneq (,$(INTEL_ARCHITECTURE)) +ifdef GNU_CC +mmx_optimized.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 +sse_optimized.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 +endif +ifdef SOLARIS_SUNPRO_CXX +mmx_optimized.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 +sse_optimized.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 +endif +endif + diff --git a/media/libsoundtouch/src/RateTransposer.cpp b/media/libsoundtouch/src/RateTransposer.cpp new file mode 100644 index 00000000000..6fb8a1eaeac --- /dev/null +++ b/media/libsoundtouch/src/RateTransposer.cpp @@ -0,0 +1,626 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sample rate transposer. Changes sample rate by using linear interpolation +/// together with anti-alias filtering (first order interpolation with anti- +/// alias filtering should be quite adequate for this application) +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2011-09-02 11:56:11 -0700 (Fri, 02 Sep 2011) $ +// File revision : $Revision: 4 $ +// +// $Id: RateTransposer.cpp 131 2011-09-02 18:56:11Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "RateTransposer.h" +#include "AAFilter.h" + +using namespace soundtouch; + + +/// A linear samplerate transposer class that uses integer arithmetics. +/// for the transposing. +class RateTransposerInteger : public RateTransposer +{ +protected: + int iSlopeCount; + int iRate; + SAMPLETYPE sPrevSampleL, sPrevSampleR; + + virtual void resetRegisters(); + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + +public: + RateTransposerInteger(); + virtual ~RateTransposerInteger(); + + /// Sets new target rate. Normal rate = 1.0, smaller values represent slower + /// rate, larger faster rates. + virtual void setRate(float newRate); + +}; + + +/// A linear samplerate transposer class that uses floating point arithmetics +/// for the transposing. +class RateTransposerFloat : public RateTransposer +{ +protected: + float fSlopeCount; + SAMPLETYPE sPrevSampleL, sPrevSampleR; + + virtual void resetRegisters(); + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + +public: + RateTransposerFloat(); + virtual ~RateTransposerFloat(); +}; + + + + +// Operator 'new' is overloaded so that it automatically creates a suitable instance +// depending on if we've a MMX/SSE/etc-capable CPU available or not. +void * RateTransposer::operator new(size_t s) +{ + ST_THROW_RT_ERROR("Error in RateTransoser::new: don't use \"new TDStretch\" directly, use \"newInstance\" to create a new instance instead!"); + return newInstance(); +} + + +RateTransposer *RateTransposer::newInstance() +{ +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + return ::new RateTransposerInteger; +#else + return ::new RateTransposerFloat; +#endif +} + + +// Constructor +RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer) +{ + numChannels = 2; + bUseAAFilter = true; + fRate = 0; + + // Instantiates the anti-alias filter with default tap length + // of 32 + pAAFilter = new AAFilter(32); +} + + + +RateTransposer::~RateTransposer() +{ + delete pAAFilter; +} + + + +/// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable +void RateTransposer::enableAAFilter(bool newMode) +{ + bUseAAFilter = newMode; +} + + +/// Returns nonzero if anti-alias filter is enabled. +bool RateTransposer::isAAFilterEnabled() const +{ + return bUseAAFilter; +} + + +AAFilter *RateTransposer::getAAFilter() +{ + return pAAFilter; +} + + + +// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower +// iRate, larger faster iRates. +void RateTransposer::setRate(float newRate) +{ + double fCutoff; + + fRate = newRate; + + // design a new anti-alias filter + if (newRate > 1.0f) + { + fCutoff = 0.5f / newRate; + } + else + { + fCutoff = 0.5f * newRate; + } + pAAFilter->setCutoffFreq(fCutoff); +} + + +// Outputs as many samples of the 'outputBuffer' as possible, and if there's +// any room left, outputs also as many of the incoming samples as possible. +// The goal is to drive the outputBuffer empty. +// +// It's allowed for 'output' and 'input' parameters to point to the same +// memory position. +/* +void RateTransposer::flushStoreBuffer() +{ + if (storeBuffer.isEmpty()) return; + + outputBuffer.moveSamples(storeBuffer); +} +*/ + + +// Adds 'nSamples' pcs of samples from the 'samples' memory position into +// the input of the object. +void RateTransposer::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + processSamples(samples, nSamples); +} + + + +// Transposes up the sample rate, causing the observed playback 'rate' of the +// sound to decrease +void RateTransposer::upsample(const SAMPLETYPE *src, uint nSamples) +{ + uint count, sizeTemp, num; + + // If the parameter 'uRate' value is smaller than 'SCALE', first transpose + // the samples and then apply the anti-alias filter to remove aliasing. + + // First check that there's enough room in 'storeBuffer' + // (+16 is to reserve some slack in the destination buffer) + sizeTemp = (uint)((float)nSamples / fRate + 16.0f); + + // Transpose the samples, store the result into the end of "storeBuffer" + count = transpose(storeBuffer.ptrEnd(sizeTemp), src, nSamples); + storeBuffer.putSamples(count); + + // Apply the anti-alias filter to samples in "store output", output the + // result to "dest" + num = storeBuffer.numSamples(); + count = pAAFilter->evaluate(outputBuffer.ptrEnd(num), + storeBuffer.ptrBegin(), num, (uint)numChannels); + outputBuffer.putSamples(count); + + // Remove the processed samples from "storeBuffer" + storeBuffer.receiveSamples(count); +} + + +// Transposes down the sample rate, causing the observed playback 'rate' of the +// sound to increase +void RateTransposer::downsample(const SAMPLETYPE *src, uint nSamples) +{ + uint count, sizeTemp; + + // If the parameter 'uRate' value is larger than 'SCALE', first apply the + // anti-alias filter to remove high frequencies (prevent them from folding + // over the lover frequencies), then transpose. + + // Add the new samples to the end of the storeBuffer + storeBuffer.putSamples(src, nSamples); + + // Anti-alias filter the samples to prevent folding and output the filtered + // data to tempBuffer. Note : because of the FIR filter length, the + // filtering routine takes in 'filter_length' more samples than it outputs. + assert(tempBuffer.isEmpty()); + sizeTemp = storeBuffer.numSamples(); + + count = pAAFilter->evaluate(tempBuffer.ptrEnd(sizeTemp), + storeBuffer.ptrBegin(), sizeTemp, (uint)numChannels); + + if (count == 0) return; + + // Remove the filtered samples from 'storeBuffer' + storeBuffer.receiveSamples(count); + + // Transpose the samples (+16 is to reserve some slack in the destination buffer) + sizeTemp = (uint)((float)nSamples / fRate + 16.0f); + count = transpose(outputBuffer.ptrEnd(sizeTemp), tempBuffer.ptrBegin(), count); + outputBuffer.putSamples(count); +} + + +// Transposes sample rate by applying anti-alias filter to prevent folding. +// Returns amount of samples returned in the "dest" buffer. +// The maximum amount of samples that can be returned at a time is set by +// the 'set_returnBuffer_size' function. +void RateTransposer::processSamples(const SAMPLETYPE *src, uint nSamples) +{ + uint count; + uint sizeReq; + + if (nSamples == 0) return; + assert(pAAFilter); + + // If anti-alias filter is turned off, simply transpose without applying + // the filter + if (bUseAAFilter == false) + { + sizeReq = (uint)((float)nSamples / fRate + 1.0f); + count = transpose(outputBuffer.ptrEnd(sizeReq), src, nSamples); + outputBuffer.putSamples(count); + return; + } + + // Transpose with anti-alias filter + if (fRate < 1.0f) + { + upsample(src, nSamples); + } + else + { + downsample(src, nSamples); + } +} + + +// Transposes the sample rate of the given samples using linear interpolation. +// Returns the number of samples returned in the "dest" buffer +inline uint RateTransposer::transpose(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + if (numChannels == 2) + { + return transposeStereo(dest, src, nSamples); + } + else + { + return transposeMono(dest, src, nSamples); + } +} + + +// Sets the number of channels, 1 = mono, 2 = stereo +void RateTransposer::setChannels(int nChannels) +{ + assert(nChannels > 0); + if (numChannels == nChannels) return; + + assert(nChannels == 1 || nChannels == 2); + numChannels = nChannels; + + storeBuffer.setChannels(numChannels); + tempBuffer.setChannels(numChannels); + outputBuffer.setChannels(numChannels); + + // Inits the linear interpolation registers + resetRegisters(); +} + + +// Clears all the samples in the object +void RateTransposer::clear() +{ + outputBuffer.clear(); + storeBuffer.clear(); +} + + +// Returns nonzero if there aren't any samples available for outputting. +int RateTransposer::isEmpty() const +{ + int res; + + res = FIFOProcessor::isEmpty(); + if (res == 0) return 0; + return storeBuffer.isEmpty(); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// RateTransposerInteger - integer arithmetic implementation +// + +/// fixed-point interpolation routine precision +#define SCALE 65536 + +// Constructor +RateTransposerInteger::RateTransposerInteger() : RateTransposer() +{ + // Notice: use local function calling syntax for sake of clarity, + // to indicate the fact that C++ constructor can't call virtual functions. + RateTransposerInteger::resetRegisters(); + RateTransposerInteger::setRate(1.0f); +} + + +RateTransposerInteger::~RateTransposerInteger() +{ +} + + +void RateTransposerInteger::resetRegisters() +{ + iSlopeCount = 0; + sPrevSampleL = + sPrevSampleR = 0; +} + + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Mono' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerInteger::transposeMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int i, used; + LONG_SAMPLETYPE temp, vol1; + + if (nSamples == 0) return 0; // no samples, no work + + used = 0; + i = 0; + + // Process the last sample saved from the previous call first... + while (iSlopeCount <= SCALE) + { + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = vol1 * sPrevSampleL + iSlopeCount * src[0]; + dest[i] = (SAMPLETYPE)(temp / SCALE); + i++; + iSlopeCount += iRate; + } + // now always (iSlopeCount > SCALE) + iSlopeCount -= SCALE; + + while (1) + { + while (iSlopeCount > SCALE) + { + iSlopeCount -= SCALE; + used ++; + if (used >= nSamples - 1) goto end; + } + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = src[used] * vol1 + iSlopeCount * src[used + 1]; + dest[i] = (SAMPLETYPE)(temp / SCALE); + + i++; + iSlopeCount += iRate; + } +end: + // Store the last sample for the next round + sPrevSampleL = src[nSamples - 1]; + + return i; +} + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Stereo' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerInteger::transposeStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int srcPos, i, used; + LONG_SAMPLETYPE temp, vol1; + + if (nSamples == 0) return 0; // no samples, no work + + used = 0; + i = 0; + + // Process the last sample saved from the sPrevSampleLious call first... + while (iSlopeCount <= SCALE) + { + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = vol1 * sPrevSampleL + iSlopeCount * src[0]; + dest[2 * i] = (SAMPLETYPE)(temp / SCALE); + temp = vol1 * sPrevSampleR + iSlopeCount * src[1]; + dest[2 * i + 1] = (SAMPLETYPE)(temp / SCALE); + i++; + iSlopeCount += iRate; + } + // now always (iSlopeCount > SCALE) + iSlopeCount -= SCALE; + + while (1) + { + while (iSlopeCount > SCALE) + { + iSlopeCount -= SCALE; + used ++; + if (used >= nSamples - 1) goto end; + } + srcPos = 2 * used; + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = src[srcPos] * vol1 + iSlopeCount * src[srcPos + 2]; + dest[2 * i] = (SAMPLETYPE)(temp / SCALE); + temp = src[srcPos + 1] * vol1 + iSlopeCount * src[srcPos + 3]; + dest[2 * i + 1] = (SAMPLETYPE)(temp / SCALE); + + i++; + iSlopeCount += iRate; + } +end: + // Store the last sample for the next round + sPrevSampleL = src[2 * nSamples - 2]; + sPrevSampleR = src[2 * nSamples - 1]; + + return i; +} + + +// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower +// iRate, larger faster iRates. +void RateTransposerInteger::setRate(float newRate) +{ + iRate = (int)(newRate * SCALE + 0.5f); + RateTransposer::setRate(newRate); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// RateTransposerFloat - floating point arithmetic implementation +// +////////////////////////////////////////////////////////////////////////////// + +// Constructor +RateTransposerFloat::RateTransposerFloat() : RateTransposer() +{ + // Notice: use local function calling syntax for sake of clarity, + // to indicate the fact that C++ constructor can't call virtual functions. + RateTransposerFloat::resetRegisters(); + RateTransposerFloat::setRate(1.0f); +} + + +RateTransposerFloat::~RateTransposerFloat() +{ +} + + +void RateTransposerFloat::resetRegisters() +{ + fSlopeCount = 0; + sPrevSampleL = + sPrevSampleR = 0; +} + + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Mono' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerFloat::transposeMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int i, used; + + used = 0; + i = 0; + + // Process the last sample saved from the previous call first... + while (fSlopeCount <= 1.0f) + { + dest[i] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleL + fSlopeCount * src[0]); + i++; + fSlopeCount += fRate; + } + fSlopeCount -= 1.0f; + + if (nSamples > 1) + { + while (1) + { + while (fSlopeCount > 1.0f) + { + fSlopeCount -= 1.0f; + used ++; + if (used >= nSamples - 1) goto end; + } + dest[i] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[used] + fSlopeCount * src[used + 1]); + i++; + fSlopeCount += fRate; + } + } +end: + // Store the last sample for the next round + sPrevSampleL = src[nSamples - 1]; + + return i; +} + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Mono' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerFloat::transposeStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int srcPos, i, used; + + if (nSamples == 0) return 0; // no samples, no work + + used = 0; + i = 0; + + // Process the last sample saved from the sPrevSampleLious call first... + while (fSlopeCount <= 1.0f) + { + dest[2 * i] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleL + fSlopeCount * src[0]); + dest[2 * i + 1] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleR + fSlopeCount * src[1]); + i++; + fSlopeCount += fRate; + } + // now always (iSlopeCount > 1.0f) + fSlopeCount -= 1.0f; + + if (nSamples > 1) + { + while (1) + { + while (fSlopeCount > 1.0f) + { + fSlopeCount -= 1.0f; + used ++; + if (used >= nSamples - 1) goto end; + } + srcPos = 2 * used; + + dest[2 * i] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[srcPos] + + fSlopeCount * src[srcPos + 2]); + dest[2 * i + 1] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[srcPos + 1] + + fSlopeCount * src[srcPos + 3]); + + i++; + fSlopeCount += fRate; + } + } +end: + // Store the last sample for the next round + sPrevSampleL = src[2 * nSamples - 2]; + sPrevSampleR = src[2 * nSamples - 1]; + + return i; +} diff --git a/media/libsoundtouch/src/RateTransposer.h b/media/libsoundtouch/src/RateTransposer.h new file mode 100644 index 00000000000..c2256210d92 --- /dev/null +++ b/media/libsoundtouch/src/RateTransposer.h @@ -0,0 +1,159 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sample rate transposer. Changes sample rate by using linear interpolation +/// together with anti-alias filtering (first order interpolation with anti- +/// alias filtering should be quite adequate for this application). +/// +/// Use either of the derived classes of 'RateTransposerInteger' or +/// 'RateTransposerFloat' for corresponding integer/floating point tranposing +/// algorithm implementation. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2009-02-21 08:00:14 -0800 (Sat, 21 Feb 2009) $ +// File revision : $Revision: 4 $ +// +// $Id: RateTransposer.h 63 2009-02-21 16:00:14Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef RateTransposer_H +#define RateTransposer_H + +#include +#include "AAFilter.h" +#include "FIFOSamplePipe.h" +#include "FIFOSampleBuffer.h" + +#include "STTypes.h" + +namespace soundtouch +{ + +/// A common linear samplerate transposer class. +/// +/// Note: Use function "RateTransposer::newInstance()" to create a new class +/// instance instead of the "new" operator; that function automatically +/// chooses a correct implementation depending on if integer or floating +/// arithmetics are to be used. +class RateTransposer : public FIFOProcessor +{ +protected: + /// Anti-alias filter object + AAFilter *pAAFilter; + + float fRate; + + int numChannels; + + /// Buffer for collecting samples to feed the anti-alias filter between + /// two batches + FIFOSampleBuffer storeBuffer; + + /// Buffer for keeping samples between transposing & anti-alias filter + FIFOSampleBuffer tempBuffer; + + /// Output sample buffer + FIFOSampleBuffer outputBuffer; + + bool bUseAAFilter; + + virtual void resetRegisters() = 0; + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) = 0; + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) = 0; + inline uint transpose(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + + void downsample(const SAMPLETYPE *src, + uint numSamples); + void upsample(const SAMPLETYPE *src, + uint numSamples); + + /// Transposes sample rate by applying anti-alias filter to prevent folding. + /// Returns amount of samples returned in the "dest" buffer. + /// The maximum amount of samples that can be returned at a time is set by + /// the 'set_returnBuffer_size' function. + void processSamples(const SAMPLETYPE *src, + uint numSamples); + + +public: + RateTransposer(); + virtual ~RateTransposer(); + + /// Operator 'new' is overloaded so that it automatically creates a suitable instance + /// depending on if we're to use integer or floating point arithmetics. + static void *operator new(size_t s); + + /// Use this function instead of "new" operator to create a new instance of this class. + /// This function automatically chooses a correct implementation, depending on if + /// integer ot floating point arithmetics are to be used. + static RateTransposer *newInstance(); + + /// Returns the output buffer object + FIFOSamplePipe *getOutput() { return &outputBuffer; }; + + /// Returns the store buffer object + FIFOSamplePipe *getStore() { return &storeBuffer; }; + + /// Return anti-alias filter object + AAFilter *getAAFilter(); + + /// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable + void enableAAFilter(bool newMode); + + /// Returns nonzero if anti-alias filter is enabled. + bool isAAFilterEnabled() const; + + /// Sets new target rate. Normal rate = 1.0, smaller values represent slower + /// rate, larger faster rates. + virtual void setRate(float newRate); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(int channels); + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position into + /// the input of the object. + void putSamples(const SAMPLETYPE *samples, uint numSamples); + + /// Clears all the samples in the object + void clear(); + + /// Returns nonzero if there aren't any samples available for outputting. + int isEmpty() const; +}; + +} + +#endif diff --git a/media/libsoundtouch/src/STTypes.h b/media/libsoundtouch/src/STTypes.h new file mode 100644 index 00000000000..bfd08f2da0e --- /dev/null +++ b/media/libsoundtouch/src/STTypes.h @@ -0,0 +1,159 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Common type definitions for SoundTouch audio processing library. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 10:01:42 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 3 $ +// +// $Id: STTypes.h 136 2012-04-01 17:01:42Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef STTypes_H +#define STTypes_H + +typedef unsigned int uint; +typedef unsigned long ulong; + +#include "soundtouch_config.h" + +#ifdef WIN32 +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + +namespace soundtouch +{ + /// Activate these undef's to overrule the possible sampletype + /// setting inherited from some other header file: + //#undef SOUNDTOUCH_INTEGER_SAMPLES + //#undef SOUNDTOUCH_FLOAT_SAMPLES + + #if !(SOUNDTOUCH_INTEGER_SAMPLES || SOUNDTOUCH_FLOAT_SAMPLES) + + /// Choose either 32bit floating point or 16bit integer sampletype + /// by choosing one of the following defines, unless this selection + /// has already been done in some other file. + //// + /// Notes: + /// - In Windows environment, choose the sample format with the + /// following defines. + /// - In GNU environment, the floating point samples are used by + /// default, but integer samples can be chosen by giving the + /// following switch to the configure script: + /// ./configure --enable-integer-samples + /// However, if you still prefer to select the sample format here + /// also in GNU environment, then please #undef the INTEGER_SAMPLE + /// and FLOAT_SAMPLE defines first as in comments above. + //#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples + #define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples + + #endif + + #if (_M_IX86 || __i386__ || __x86_64__ || _M_X64) + /// Define this to allow X86-specific assembler/intrinsic optimizations. + /// Notice that library contains also usual C++ versions of each of these + /// these routines, so if you're having difficulties getting the optimized + /// routines compiled for whatever reason, you may disable these optimizations + /// to make the library compile. + + #define SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS 1 + + /// In GNU environment, allow the user to override this setting by + /// giving the following switch to the configure script: + /// ./configure --disable-x86-optimizations + /// ./configure --enable-x86-optimizations=no + #ifdef SOUNDTOUCH_DISABLE_X86_OPTIMIZATIONS + #undef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + #endif + #else + /// Always disable optimizations when not using a x86 systems. + #undef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + + #endif + + // If defined, allows the SIMD-optimized routines to take minor shortcuts + // for improved performance. Undefine to require faithfully similar SIMD + // calculations as in normal C implementation. + #define SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION 1 + + + #ifdef SOUNDTOUCH_INTEGER_SAMPLES + // 16bit integer sample type + typedef short SAMPLETYPE; + // data type for sample accumulation: Use 32bit integer to prevent overflows + typedef long LONG_SAMPLETYPE; + + #ifdef SOUNDTOUCH_FLOAT_SAMPLES + // check that only one sample type is defined + #error "conflicting sample types defined" + #endif // SOUNDTOUCH_FLOAT_SAMPLES + + #ifdef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + // Allow MMX optimizations + #define SOUNDTOUCH_ALLOW_MMX 1 + #endif + + #else + + // floating point samples + typedef float SAMPLETYPE; + // data type for sample accumulation: Use double to utilize full precision. + typedef double LONG_SAMPLETYPE; + + #ifdef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + // Allow SSE optimizations + #define SOUNDTOUCH_ALLOW_SSE 1 + #endif + + #endif // SOUNDTOUCH_INTEGER_SAMPLES + +} + +// define ST_NO_EXCEPTION_HANDLING switch to disable throwing std exceptions: +#define ST_NO_EXCEPTION_HANDLING 1 +#ifdef ST_NO_EXCEPTION_HANDLING + // Exceptions disabled. Throw asserts instead if enabled. + #include + #define ST_THROW_RT_ERROR(x) {assert((const char *)x);} +#else + // use c++ standard exceptions + #include + #define ST_THROW_RT_ERROR(x) {throw std::runtime_error(x);} +#endif + +// When this #define is active, eliminates a clicking sound when the "rate" or "pitch" +// parameter setting crosses from value <1 to >=1 or vice versa during processing. +// Default is off as such crossover is untypical case and involves a slight sound +// quality compromise. +//#define SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER 1 + +#endif diff --git a/media/libsoundtouch/src/SoundTouch.cpp b/media/libsoundtouch/src/SoundTouch.cpp new file mode 100644 index 00000000000..46de76cb2d4 --- /dev/null +++ b/media/libsoundtouch/src/SoundTouch.cpp @@ -0,0 +1,501 @@ +////////////////////////////////////////////////////////////////////////////// +/// +/// SoundTouch - main class for tempo/pitch/rate adjusting routines. +/// +/// Notes: +/// - Initialize the SoundTouch object instance by setting up the sound stream +/// parameters with functions 'setSampleRate' and 'setChannels', then set +/// desired tempo/pitch/rate settings with the corresponding functions. +/// +/// - The SoundTouch class behaves like a first-in-first-out pipeline: The +/// samples that are to be processed are fed into one of the pipe by calling +/// function 'putSamples', while the ready processed samples can be read +/// from the other end of the pipeline with function 'receiveSamples'. +/// +/// - The SoundTouch processing classes require certain sized 'batches' of +/// samples in order to process the sound. For this reason the classes buffer +/// incoming samples until there are enough of samples available for +/// processing, then they carry out the processing step and consequently +/// make the processed samples available for outputting. +/// +/// - For the above reason, the processing routines introduce a certain +/// 'latency' between the input and output, so that the samples input to +/// SoundTouch may not be immediately available in the output, and neither +/// the amount of outputtable samples may not immediately be in direct +/// relationship with the amount of previously input samples. +/// +/// - The tempo/pitch/rate control parameters can be altered during processing. +/// Please notice though that they aren't currently protected by semaphores, +/// so in multi-thread application external semaphore protection may be +/// required. +/// +/// - This class utilizes classes 'TDStretch' for tempo change (without modifying +/// pitch) and 'RateTransposer' for changing the playback rate (that is, both +/// tempo and pitch in the same ratio) of the sound. The third available control +/// 'pitch' (change pitch but maintain tempo) is produced by a combination of +/// combining the two other controls. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: SoundTouch.cpp 143 2012-06-13 19:29:53Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "SoundTouch.h" +#include "TDStretch.h" +#include "RateTransposer.h" +#include "cpu_detect.h" + +using namespace soundtouch; + +/// test if two floating point numbers are equal +#define TEST_FLOAT_EQUAL(a, b) (fabs(a - b) < 1e-10) + + +/// Print library version string for autoconf +extern "C" void soundtouch_ac_test() +{ + printf("SoundTouch Version: %s\n",SOUNDTOUCH_VERSION); +} + + +SoundTouch::SoundTouch() +{ + // Initialize rate transposer and tempo changer instances + + pRateTransposer = RateTransposer::newInstance(); + pTDStretch = TDStretch::newInstance(); + + setOutPipe(pTDStretch); + + rate = tempo = 0; + + virtualPitch = + virtualRate = + virtualTempo = 1.0; + + calcEffectiveRateAndTempo(); + + channels = 0; + bSrateSet = false; +} + + + +SoundTouch::~SoundTouch() +{ + delete pRateTransposer; + delete pTDStretch; +} + + + +/// Get SoundTouch library version string +const char *SoundTouch::getVersionString() +{ + static const char *_version = SOUNDTOUCH_VERSION; + + return _version; +} + + +/// Get SoundTouch library version Id +uint SoundTouch::getVersionId() +{ + return SOUNDTOUCH_VERSION_ID; +} + + +// Sets the number of channels, 1 = mono, 2 = stereo +void SoundTouch::setChannels(uint numChannels) +{ + if (numChannels != 1 && numChannels != 2) + { + ST_THROW_RT_ERROR("Illegal number of channels"); + } + channels = numChannels; + pRateTransposer->setChannels((int)numChannels); + pTDStretch->setChannels((int)numChannels); +} + + + +// Sets new rate control value. Normal rate = 1.0, smaller values +// represent slower rate, larger faster rates. +void SoundTouch::setRate(float newRate) +{ + virtualRate = newRate; + calcEffectiveRateAndTempo(); +} + + + +// Sets new rate control value as a difference in percents compared +// to the original rate (-50 .. +100 %) +void SoundTouch::setRateChange(float newRate) +{ + virtualRate = 1.0f + 0.01f * newRate; + calcEffectiveRateAndTempo(); +} + + + +// Sets new tempo control value. Normal tempo = 1.0, smaller values +// represent slower tempo, larger faster tempo. +void SoundTouch::setTempo(float newTempo) +{ + virtualTempo = newTempo; + calcEffectiveRateAndTempo(); +} + + + +// Sets new tempo control value as a difference in percents compared +// to the original tempo (-50 .. +100 %) +void SoundTouch::setTempoChange(float newTempo) +{ + virtualTempo = 1.0f + 0.01f * newTempo; + calcEffectiveRateAndTempo(); +} + + + +// Sets new pitch control value. Original pitch = 1.0, smaller values +// represent lower pitches, larger values higher pitch. +void SoundTouch::setPitch(float newPitch) +{ + virtualPitch = newPitch; + calcEffectiveRateAndTempo(); +} + + + +// Sets pitch change in octaves compared to the original pitch +// (-1.00 .. +1.00) +void SoundTouch::setPitchOctaves(float newPitch) +{ + virtualPitch = (float)exp(0.69314718056f * newPitch); + calcEffectiveRateAndTempo(); +} + + + +// Sets pitch change in semi-tones compared to the original pitch +// (-12 .. +12) +void SoundTouch::setPitchSemiTones(int newPitch) +{ + setPitchOctaves((float)newPitch / 12.0f); +} + + + +void SoundTouch::setPitchSemiTones(float newPitch) +{ + setPitchOctaves(newPitch / 12.0f); +} + + +// Calculates 'effective' rate and tempo values from the +// nominal control values. +void SoundTouch::calcEffectiveRateAndTempo() +{ + float oldTempo = tempo; + float oldRate = rate; + + tempo = virtualTempo / virtualPitch; + rate = virtualPitch * virtualRate; + + if (!TEST_FLOAT_EQUAL(rate,oldRate)) pRateTransposer->setRate(rate); + if (!TEST_FLOAT_EQUAL(tempo, oldTempo)) pTDStretch->setTempo(tempo); + +#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER + if (rate <= 1.0f) + { + if (output != pTDStretch) + { + FIFOSamplePipe *tempoOut; + + assert(output == pRateTransposer); + // move samples in the current output buffer to the output of pTDStretch + tempoOut = pTDStretch->getOutput(); + tempoOut->moveSamples(*output); + // move samples in pitch transposer's store buffer to tempo changer's input + pTDStretch->moveSamples(*pRateTransposer->getStore()); + + output = pTDStretch; + } + } + else +#endif + { + if (output != pRateTransposer) + { + FIFOSamplePipe *transOut; + + assert(output == pTDStretch); + // move samples in the current output buffer to the output of pRateTransposer + transOut = pRateTransposer->getOutput(); + transOut->moveSamples(*output); + // move samples in tempo changer's input to pitch transposer's input + pRateTransposer->moveSamples(*pTDStretch->getInput()); + + output = pRateTransposer; + } + } +} + + +// Sets sample rate. +void SoundTouch::setSampleRate(uint srate) +{ + bSrateSet = true; + // set sample rate, leave other tempo changer parameters as they are. + pTDStretch->setParameters((int)srate); +} + + +// Adds 'numSamples' pcs of samples from the 'samples' memory position into +// the input of the object. +void SoundTouch::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + if (bSrateSet == false) + { + ST_THROW_RT_ERROR("SoundTouch : Sample rate not defined"); + } + else if (channels == 0) + { + ST_THROW_RT_ERROR("SoundTouch : Number of channels not defined"); + } + + // Transpose the rate of the new samples if necessary + /* Bypass the nominal setting - can introduce a click in sound when tempo/pitch control crosses the nominal value... + if (rate == 1.0f) + { + // The rate value is same as the original, simply evaluate the tempo changer. + assert(output == pTDStretch); + if (pRateTransposer->isEmpty() == 0) + { + // yet flush the last samples in the pitch transposer buffer + // (may happen if 'rate' changes from a non-zero value to zero) + pTDStretch->moveSamples(*pRateTransposer); + } + pTDStretch->putSamples(samples, nSamples); + } + */ +#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER + else if (rate <= 1.0f) + { + // transpose the rate down, output the transposed sound to tempo changer buffer + assert(output == pTDStretch); + pRateTransposer->putSamples(samples, nSamples); + pTDStretch->moveSamples(*pRateTransposer); + } + else +#endif + { + // evaluate the tempo changer, then transpose the rate up, + assert(output == pRateTransposer); + pTDStretch->putSamples(samples, nSamples); + pRateTransposer->moveSamples(*pTDStretch); + } +} + + +// Flushes the last samples from the processing pipeline to the output. +// Clears also the internal processing buffers. +// +// Note: This function is meant for extracting the last samples of a sound +// stream. This function may introduce additional blank samples in the end +// of the sound stream, and thus it's not recommended to call this function +// in the middle of a sound stream. +void SoundTouch::flush() +{ + int i; + int nUnprocessed; + int nOut; + SAMPLETYPE buff[64*2]; // note: allocate 2*64 to cater 64 sample frames of stereo sound + + // check how many samples still await processing, and scale + // that by tempo & rate to get expected output sample count + nUnprocessed = numUnprocessedSamples(); + nUnprocessed = (int)((double)nUnprocessed / (tempo * rate) + 0.5); + + nOut = numSamples(); // ready samples currently in buffer ... + nOut += nUnprocessed; // ... and how many we expect there to be in the end + + memset(buff, 0, 64 * channels * sizeof(SAMPLETYPE)); + // "Push" the last active samples out from the processing pipeline by + // feeding blank samples into the processing pipeline until new, + // processed samples appear in the output (not however, more than + // 8ksamples in any case) + for (i = 0; i < 128; i ++) + { + putSamples(buff, 64); + if ((int)numSamples() >= nOut) + { + // Enough new samples have appeared into the output! + // As samples come from processing with bigger chunks, now truncate it + // back to maximum "nOut" samples to improve duration accuracy + adjustAmountOfSamples(nOut); + + // finish + break; + } + } + + // Clear working buffers + pRateTransposer->clear(); + pTDStretch->clearInput(); + // yet leave the 'tempoChanger' output intouched as that's where the + // flushed samples are! +} + + +// Changes a setting controlling the processing system behaviour. See the +// 'SETTING_...' defines for available setting ID's. +bool SoundTouch::setSetting(int settingId, int value) +{ + int sampleRate, sequenceMs, seekWindowMs, overlapMs; + + // read current tdstretch routine parameters + pTDStretch->getParameters(&sampleRate, &sequenceMs, &seekWindowMs, &overlapMs); + + switch (settingId) + { + case SETTING_USE_AA_FILTER : + // enables / disabless anti-alias filter + pRateTransposer->enableAAFilter((value != 0) ? true : false); + return true; + + case SETTING_AA_FILTER_LENGTH : + // sets anti-alias filter length + pRateTransposer->getAAFilter()->setLength(value); + return true; + + case SETTING_USE_QUICKSEEK : + // enables / disables tempo routine quick seeking algorithm + pTDStretch->enableQuickSeek((value != 0) ? true : false); + return true; + + case SETTING_SEQUENCE_MS: + // change time-stretch sequence duration parameter + pTDStretch->setParameters(sampleRate, value, seekWindowMs, overlapMs); + return true; + + case SETTING_SEEKWINDOW_MS: + // change time-stretch seek window length parameter + pTDStretch->setParameters(sampleRate, sequenceMs, value, overlapMs); + return true; + + case SETTING_OVERLAP_MS: + // change time-stretch overlap length parameter + pTDStretch->setParameters(sampleRate, sequenceMs, seekWindowMs, value); + return true; + + default : + return false; + } +} + + +// Reads a setting controlling the processing system behaviour. See the +// 'SETTING_...' defines for available setting ID's. +// +// Returns the setting value. +int SoundTouch::getSetting(int settingId) const +{ + int temp; + + switch (settingId) + { + case SETTING_USE_AA_FILTER : + return (uint)pRateTransposer->isAAFilterEnabled(); + + case SETTING_AA_FILTER_LENGTH : + return pRateTransposer->getAAFilter()->getLength(); + + case SETTING_USE_QUICKSEEK : + return (uint) pTDStretch->isQuickSeekEnabled(); + + case SETTING_SEQUENCE_MS: + pTDStretch->getParameters(NULL, &temp, NULL, NULL); + return temp; + + case SETTING_SEEKWINDOW_MS: + pTDStretch->getParameters(NULL, NULL, &temp, NULL); + return temp; + + case SETTING_OVERLAP_MS: + pTDStretch->getParameters(NULL, NULL, NULL, &temp); + return temp; + + case SETTING_NOMINAL_INPUT_SEQUENCE : + return pTDStretch->getInputSampleReq(); + + case SETTING_NOMINAL_OUTPUT_SEQUENCE : + return pTDStretch->getOutputBatchSize(); + + default : + return 0; + } +} + + +// Clears all the samples in the object's output and internal processing +// buffers. +void SoundTouch::clear() +{ + pRateTransposer->clear(); + pTDStretch->clear(); +} + + + +/// Returns number of samples currently unprocessed. +uint SoundTouch::numUnprocessedSamples() const +{ + FIFOSamplePipe * psp; + if (pTDStretch) + { + psp = pTDStretch->getInput(); + if (psp) + { + return psp->numSamples(); + } + } + return 0; +} diff --git a/media/libsoundtouch/src/SoundTouch.h b/media/libsoundtouch/src/SoundTouch.h new file mode 100644 index 00000000000..42319ad8521 --- /dev/null +++ b/media/libsoundtouch/src/SoundTouch.h @@ -0,0 +1,277 @@ +////////////////////////////////////////////////////////////////////////////// +/// +/// SoundTouch - main class for tempo/pitch/rate adjusting routines. +/// +/// Notes: +/// - Initialize the SoundTouch object instance by setting up the sound stream +/// parameters with functions 'setSampleRate' and 'setChannels', then set +/// desired tempo/pitch/rate settings with the corresponding functions. +/// +/// - The SoundTouch class behaves like a first-in-first-out pipeline: The +/// samples that are to be processed are fed into one of the pipe by calling +/// function 'putSamples', while the ready processed samples can be read +/// from the other end of the pipeline with function 'receiveSamples'. +/// +/// - The SoundTouch processing classes require certain sized 'batches' of +/// samples in order to process the sound. For this reason the classes buffer +/// incoming samples until there are enough of samples available for +/// processing, then they carry out the processing step and consequently +/// make the processed samples available for outputting. +/// +/// - For the above reason, the processing routines introduce a certain +/// 'latency' between the input and output, so that the samples input to +/// SoundTouch may not be immediately available in the output, and neither +/// the amount of outputtable samples may not immediately be in direct +/// relationship with the amount of previously input samples. +/// +/// - The tempo/pitch/rate control parameters can be altered during processing. +/// Please notice though that they aren't currently protected by semaphores, +/// so in multi-thread application external semaphore protection may be +/// required. +/// +/// - This class utilizes classes 'TDStretch' for tempo change (without modifying +/// pitch) and 'RateTransposer' for changing the playback rate (that is, both +/// tempo and pitch in the same ratio) of the sound. The third available control +/// 'pitch' (change pitch but maintain tempo) is produced by a combination of +/// combining the two other controls. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-04 12:47:28 -0700 (Wed, 04 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: SoundTouch.h 141 2012-04-04 19:47:28Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef SoundTouch_H +#define SoundTouch_H + +#include "FIFOSamplePipe.h" +#include "STTypes.h" + +namespace soundtouch +{ + +/// Soundtouch library version string +#define SOUNDTOUCH_VERSION "1.7.0" + +/// SoundTouch library version id +#define SOUNDTOUCH_VERSION_ID (10700) + +// +// Available setting IDs for the 'setSetting' & 'get_setting' functions: + +/// Enable/disable anti-alias filter in pitch transposer (0 = disable) +#define SETTING_USE_AA_FILTER 0 + +/// Pitch transposer anti-alias filter length (8 .. 128 taps, default = 32) +#define SETTING_AA_FILTER_LENGTH 1 + +/// Enable/disable quick seeking algorithm in tempo changer routine +/// (enabling quick seeking lowers CPU utilization but causes a minor sound +/// quality compromising) +#define SETTING_USE_QUICKSEEK 2 + +/// Time-stretch algorithm single processing sequence length in milliseconds. This determines +/// to how long sequences the original sound is chopped in the time-stretch algorithm. +/// See "STTypes.h" or README for more information. +#define SETTING_SEQUENCE_MS 3 + +/// Time-stretch algorithm seeking window length in milliseconds for algorithm that finds the +/// best possible overlapping location. This determines from how wide window the algorithm +/// may look for an optimal joining location when mixing the sound sequences back together. +/// See "STTypes.h" or README for more information. +#define SETTING_SEEKWINDOW_MS 4 + +/// Time-stretch algorithm overlap length in milliseconds. When the chopped sound sequences +/// are mixed back together, to form a continuous sound stream, this parameter defines over +/// how long period the two consecutive sequences are let to overlap each other. +/// See "STTypes.h" or README for more information. +#define SETTING_OVERLAP_MS 5 + + +/// Call "getSetting" with this ID to query nominal average processing sequence +/// size in samples. This value tells approcimate value how many input samples +/// SoundTouch needs to gather before it does DSP processing run for the sample batch. +/// +/// Notices: +/// - This is read-only parameter, i.e. setSetting ignores this parameter +/// - Returned value is approximate average value, exact processing batch +/// size may wary from time to time +/// - This parameter value is not constant but may change depending on +/// tempo/pitch/rate/samplerate settings. +#define SETTING_NOMINAL_INPUT_SEQUENCE 6 + + +/// Call "getSetting" with this ID to query nominal average processing output +/// size in samples. This value tells approcimate value how many output samples +/// SoundTouch outputs once it does DSP processing run for a batch of input samples. +/// +/// Notices: +/// - This is read-only parameter, i.e. setSetting ignores this parameter +/// - Returned value is approximate average value, exact processing batch +/// size may wary from time to time +/// - This parameter value is not constant but may change depending on +/// tempo/pitch/rate/samplerate settings. +#define SETTING_NOMINAL_OUTPUT_SEQUENCE 7 + +class EXPORT SoundTouch : public FIFOProcessor +{ +private: + /// Rate transposer class instance + class RateTransposer *pRateTransposer; + + /// Time-stretch class instance + class TDStretch *pTDStretch; + + /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. + float virtualRate; + + /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. + float virtualTempo; + + /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. + float virtualPitch; + + /// Flag: Has sample rate been set? + bool bSrateSet; + + /// Calculates effective rate & tempo valuescfrom 'virtualRate', 'virtualTempo' and + /// 'virtualPitch' parameters. + void calcEffectiveRateAndTempo(); + +protected : + /// Number of channels + uint channels; + + /// Effective 'rate' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch' + float rate; + + /// Effective 'tempo' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch' + float tempo; + +public: + SoundTouch(); + virtual ~SoundTouch(); + + /// Get SoundTouch library version string + static const char *getVersionString(); + + /// Get SoundTouch library version Id + static uint getVersionId(); + + /// Sets new rate control value. Normal rate = 1.0, smaller values + /// represent slower rate, larger faster rates. + void setRate(float newRate); + + /// Sets new tempo control value. Normal tempo = 1.0, smaller values + /// represent slower tempo, larger faster tempo. + void setTempo(float newTempo); + + /// Sets new rate control value as a difference in percents compared + /// to the original rate (-50 .. +100 %) + void setRateChange(float newRate); + + /// Sets new tempo control value as a difference in percents compared + /// to the original tempo (-50 .. +100 %) + void setTempoChange(float newTempo); + + /// Sets new pitch control value. Original pitch = 1.0, smaller values + /// represent lower pitches, larger values higher pitch. + void setPitch(float newPitch); + + /// Sets pitch change in octaves compared to the original pitch + /// (-1.00 .. +1.00) + void setPitchOctaves(float newPitch); + + /// Sets pitch change in semi-tones compared to the original pitch + /// (-12 .. +12) + void setPitchSemiTones(int newPitch); + void setPitchSemiTones(float newPitch); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(uint numChannels); + + /// Sets sample rate. + void setSampleRate(uint srate); + + /// Flushes the last samples from the processing pipeline to the output. + /// Clears also the internal processing buffers. + // + /// Note: This function is meant for extracting the last samples of a sound + /// stream. This function may introduce additional blank samples in the end + /// of the sound stream, and thus it's not recommended to call this function + /// in the middle of a sound stream. + void flush(); + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position into + /// the input of the object. Notice that sample rate _has_to_ be set before + /// calling this function, otherwise throws a runtime_error exception. + virtual void putSamples( + const SAMPLETYPE *samples, ///< Pointer to sample buffer. + uint numSamples ///< Number of samples in buffer. Notice + ///< that in case of stereo-sound a single sample + ///< contains data for both channels. + ); + + /// Clears all the samples in the object's output and internal processing + /// buffers. + virtual void clear(); + + /// Changes a setting controlling the processing system behaviour. See the + /// 'SETTING_...' defines for available setting ID's. + /// + /// \return 'true' if the setting was succesfully changed + bool setSetting(int settingId, ///< Setting ID number. see SETTING_... defines. + int value ///< New setting value. + ); + + /// Reads a setting controlling the processing system behaviour. See the + /// 'SETTING_...' defines for available setting ID's. + /// + /// \return the setting value. + int getSetting(int settingId ///< Setting ID number, see SETTING_... defines. + ) const; + + /// Returns number of samples currently unprocessed. + virtual uint numUnprocessedSamples() const; + + + /// Other handy functions that are implemented in the ancestor classes (see + /// classes 'FIFOProcessor' and 'FIFOSamplePipe') + /// + /// - receiveSamples() : Use this function to receive 'ready' processed samples from SoundTouch. + /// - numSamples() : Get number of 'ready' samples that can be received with + /// function 'receiveSamples()' + /// - isEmpty() : Returns nonzero if there aren't any 'ready' samples. + /// - clear() : Clears all samples from ready/processing buffers. +}; + +} +#endif diff --git a/media/libsoundtouch/src/TDStretch.cpp b/media/libsoundtouch/src/TDStretch.cpp new file mode 100644 index 00000000000..bec03ae2d6b --- /dev/null +++ b/media/libsoundtouch/src/TDStretch.cpp @@ -0,0 +1,808 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo +/// while maintaining the original pitch by using a time domain WSOLA-like +/// method with several performance-increasing tweaks. +/// +/// Note : MMX optimized functions reside in a separate, platform-specific +/// file, e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 1.12 $ +// +// $Id: TDStretch.cpp 137 2012-04-01 19:49:30Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "STTypes.h" +#include "cpu_detect.h" +#include "TDStretch.h" + +#include + +using namespace soundtouch; + +#define max(x, y) (((x) > (y)) ? (x) : (y)) + + +/***************************************************************************** + * + * Constant definitions + * + *****************************************************************************/ + +// Table for the hierarchical mixing position seeking algorithm +static const short _scanOffsets[5][24]={ + { 124, 186, 248, 310, 372, 434, 496, 558, 620, 682, 744, 806, + 868, 930, 992, 1054, 1116, 1178, 1240, 1302, 1364, 1426, 1488, 0}, + {-100, -75, -50, -25, 25, 50, 75, 100, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { -20, -15, -10, -5, 5, 10, 15, 20, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { -4, -3, -2, -1, 1, 2, 3, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 121, 114, 97, 114, 98, 105, 108, 32, 104, 99, 117, 111, + 116, 100, 110, 117, 111, 115, 0, 0, 0, 0, 0, 0}}; + +/***************************************************************************** + * + * Implementation of the class 'TDStretch' + * + *****************************************************************************/ + + +TDStretch::TDStretch() : FIFOProcessor(&outputBuffer) +{ + bQuickSeek = false; + channels = 2; + + pMidBuffer = NULL; + pMidBufferUnaligned = NULL; + overlapLength = 0; + + bAutoSeqSetting = true; + bAutoSeekSetting = true; + +// outDebt = 0; + skipFract = 0; + + tempo = 1.0f; + setParameters(44100, DEFAULT_SEQUENCE_MS, DEFAULT_SEEKWINDOW_MS, DEFAULT_OVERLAP_MS); + setTempo(1.0f); + + clear(); +} + + + +TDStretch::~TDStretch() +{ + delete[] pMidBufferUnaligned; +} + + + +// Sets routine control parameters. These control are certain time constants +// defining how the sound is stretched to the desired duration. +// +// 'sampleRate' = sample rate of the sound +// 'sequenceMS' = one processing sequence length in milliseconds (default = 82 ms) +// 'seekwindowMS' = seeking window length for scanning the best overlapping +// position (default = 28 ms) +// 'overlapMS' = overlapping length (default = 12 ms) + +void TDStretch::setParameters(int aSampleRate, int aSequenceMS, + int aSeekWindowMS, int aOverlapMS) +{ + // accept only positive parameter values - if zero or negative, use old values instead + if (aSampleRate > 0) this->sampleRate = aSampleRate; + if (aOverlapMS > 0) this->overlapMs = aOverlapMS; + + if (aSequenceMS > 0) + { + this->sequenceMs = aSequenceMS; + bAutoSeqSetting = false; + } + else if (aSequenceMS == 0) + { + // if zero, use automatic setting + bAutoSeqSetting = true; + } + + if (aSeekWindowMS > 0) + { + this->seekWindowMs = aSeekWindowMS; + bAutoSeekSetting = false; + } + else if (aSeekWindowMS == 0) + { + // if zero, use automatic setting + bAutoSeekSetting = true; + } + + calcSeqParameters(); + + calculateOverlapLength(overlapMs); + + // set tempo to recalculate 'sampleReq' + setTempo(tempo); + +} + + + +/// Get routine control parameters, see setParameters() function. +/// Any of the parameters to this function can be NULL, in such case corresponding parameter +/// value isn't returned. +void TDStretch::getParameters(int *pSampleRate, int *pSequenceMs, int *pSeekWindowMs, int *pOverlapMs) const +{ + if (pSampleRate) + { + *pSampleRate = sampleRate; + } + + if (pSequenceMs) + { + *pSequenceMs = (bAutoSeqSetting) ? (USE_AUTO_SEQUENCE_LEN) : sequenceMs; + } + + if (pSeekWindowMs) + { + *pSeekWindowMs = (bAutoSeekSetting) ? (USE_AUTO_SEEKWINDOW_LEN) : seekWindowMs; + } + + if (pOverlapMs) + { + *pOverlapMs = overlapMs; + } +} + + +// Overlaps samples in 'midBuffer' with the samples in 'pInput' +void TDStretch::overlapMono(SAMPLETYPE *pOutput, const SAMPLETYPE *pInput) const +{ + int i; + SAMPLETYPE m1, m2; + + m1 = (SAMPLETYPE)0; + m2 = (SAMPLETYPE)overlapLength; + + for (i = 0; i < overlapLength ; i ++) + { + pOutput[i] = (pInput[i] * m1 + pMidBuffer[i] * m2 ) / overlapLength; + m1 += 1; + m2 -= 1; + } +} + + + +void TDStretch::clearMidBuffer() +{ + memset(pMidBuffer, 0, 2 * sizeof(SAMPLETYPE) * overlapLength); +} + + +void TDStretch::clearInput() +{ + inputBuffer.clear(); + clearMidBuffer(); +} + + +// Clears the sample buffers +void TDStretch::clear() +{ + outputBuffer.clear(); + clearInput(); +} + + + +// Enables/disables the quick position seeking algorithm. Zero to disable, nonzero +// to enable +void TDStretch::enableQuickSeek(bool enable) +{ + bQuickSeek = enable; +} + + +// Returns nonzero if the quick seeking algorithm is enabled. +bool TDStretch::isQuickSeekEnabled() const +{ + return bQuickSeek; +} + + +// Seeks for the optimal overlap-mixing position. +int TDStretch::seekBestOverlapPosition(const SAMPLETYPE *refPos) +{ + if (bQuickSeek) + { + return seekBestOverlapPositionQuick(refPos); + } + else + { + return seekBestOverlapPositionFull(refPos); + } +} + + +// Overlaps samples in 'midBuffer' with the samples in 'pInputBuffer' at position +// of 'ovlPos'. +inline void TDStretch::overlap(SAMPLETYPE *pOutput, const SAMPLETYPE *pInput, uint ovlPos) const +{ + if (channels == 2) + { + // stereo sound + overlapStereo(pOutput, pInput + 2 * ovlPos); + } else { + // mono sound. + overlapMono(pOutput, pInput + ovlPos); + } +} + + + +// Seeks for the optimal overlap-mixing position. The 'stereo' version of the +// routine +// +// The best position is determined as the position where the two overlapped +// sample sequences are 'most alike', in terms of the highest cross-correlation +// value over the overlapping period +int TDStretch::seekBestOverlapPositionFull(const SAMPLETYPE *refPos) +{ + int bestOffs; + double bestCorr, corr; + int i; + + bestCorr = FLT_MIN; + bestOffs = 0; + + // Scans for the best correlation value by testing each possible position + // over the permitted range. + for (i = 0; i < seekLength; i ++) + { + // Calculates correlation value for the mixing position corresponding + // to 'i' + corr = calcCrossCorr(refPos + channels * i, pMidBuffer); + // heuristic rule to slightly favour values close to mid of the range + double tmp = (double)(2 * i - seekLength) / (double)seekLength; + corr = ((corr + 0.1) * (1.0 - 0.25 * tmp * tmp)); + + // Checks for the highest correlation value + if (corr > bestCorr) + { + bestCorr = corr; + bestOffs = i; + } + } + // clear cross correlation routine state if necessary (is so e.g. in MMX routines). + clearCrossCorrState(); + + return bestOffs; +} + + +// Seeks for the optimal overlap-mixing position. The 'stereo' version of the +// routine +// +// The best position is determined as the position where the two overlapped +// sample sequences are 'most alike', in terms of the highest cross-correlation +// value over the overlapping period +int TDStretch::seekBestOverlapPositionQuick(const SAMPLETYPE *refPos) +{ + int j; + int bestOffs; + double bestCorr, corr; + int scanCount, corrOffset, tempOffset; + + bestCorr = FLT_MIN; + bestOffs = _scanOffsets[0][0]; + corrOffset = 0; + tempOffset = 0; + + // Scans for the best correlation value using four-pass hierarchical search. + // + // The look-up table 'scans' has hierarchical position adjusting steps. + // In first pass the routine searhes for the highest correlation with + // relatively coarse steps, then rescans the neighbourhood of the highest + // correlation with better resolution and so on. + for (scanCount = 0;scanCount < 4; scanCount ++) + { + j = 0; + while (_scanOffsets[scanCount][j]) + { + tempOffset = corrOffset + _scanOffsets[scanCount][j]; + if (tempOffset >= seekLength) break; + + // Calculates correlation value for the mixing position corresponding + // to 'tempOffset' + corr = (double)calcCrossCorr(refPos + channels * tempOffset, pMidBuffer); + // heuristic rule to slightly favour values close to mid of the range + double tmp = (double)(2 * tempOffset - seekLength) / seekLength; + corr = ((corr + 0.1) * (1.0 - 0.25 * tmp * tmp)); + + // Checks for the highest correlation value + if (corr > bestCorr) + { + bestCorr = corr; + bestOffs = tempOffset; + } + j ++; + } + corrOffset = bestOffs; + } + // clear cross correlation routine state if necessary (is so e.g. in MMX routines). + clearCrossCorrState(); + + return bestOffs; +} + + + +/// clear cross correlation routine state if necessary +void TDStretch::clearCrossCorrState() +{ + // default implementation is empty. +} + + +/// Calculates processing sequence length according to tempo setting +void TDStretch::calcSeqParameters() +{ + // Adjust tempo param according to tempo, so that variating processing sequence length is used + // at varius tempo settings, between the given low...top limits + #define AUTOSEQ_TEMPO_LOW 0.5 // auto setting low tempo range (-50%) + #define AUTOSEQ_TEMPO_TOP 2.0 // auto setting top tempo range (+100%) + + // sequence-ms setting values at above low & top tempo + #define AUTOSEQ_AT_MIN 125.0 + #define AUTOSEQ_AT_MAX 50.0 + #define AUTOSEQ_K ((AUTOSEQ_AT_MAX - AUTOSEQ_AT_MIN) / (AUTOSEQ_TEMPO_TOP - AUTOSEQ_TEMPO_LOW)) + #define AUTOSEQ_C (AUTOSEQ_AT_MIN - (AUTOSEQ_K) * (AUTOSEQ_TEMPO_LOW)) + + // seek-window-ms setting values at above low & top tempo + #define AUTOSEEK_AT_MIN 25.0 + #define AUTOSEEK_AT_MAX 15.0 + #define AUTOSEEK_K ((AUTOSEEK_AT_MAX - AUTOSEEK_AT_MIN) / (AUTOSEQ_TEMPO_TOP - AUTOSEQ_TEMPO_LOW)) + #define AUTOSEEK_C (AUTOSEEK_AT_MIN - (AUTOSEEK_K) * (AUTOSEQ_TEMPO_LOW)) + + #define CHECK_LIMITS(x, mi, ma) (((x) < (mi)) ? (mi) : (((x) > (ma)) ? (ma) : (x))) + + double seq, seek; + + if (bAutoSeqSetting) + { + seq = AUTOSEQ_C + AUTOSEQ_K * tempo; + seq = CHECK_LIMITS(seq, AUTOSEQ_AT_MAX, AUTOSEQ_AT_MIN); + sequenceMs = (int)(seq + 0.5); + } + + if (bAutoSeekSetting) + { + seek = AUTOSEEK_C + AUTOSEEK_K * tempo; + seek = CHECK_LIMITS(seek, AUTOSEEK_AT_MAX, AUTOSEEK_AT_MIN); + seekWindowMs = (int)(seek + 0.5); + } + + // Update seek window lengths + seekWindowLength = (sampleRate * sequenceMs) / 1000; + if (seekWindowLength < 2 * overlapLength) + { + seekWindowLength = 2 * overlapLength; + } + seekLength = (sampleRate * seekWindowMs) / 1000; +} + + + +// Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower +// tempo, larger faster tempo. +void TDStretch::setTempo(float newTempo) +{ + int intskip; + + tempo = newTempo; + + // Calculate new sequence duration + calcSeqParameters(); + + // Calculate ideal skip length (according to tempo value) + nominalSkip = tempo * (seekWindowLength - overlapLength); + intskip = (int)(nominalSkip + 0.5f); + + // Calculate how many samples are needed in the 'inputBuffer' to + // process another batch of samples + //sampleReq = max(intskip + overlapLength, seekWindowLength) + seekLength / 2; + sampleReq = max(intskip + overlapLength, seekWindowLength) + seekLength; +} + + + +// Sets the number of channels, 1 = mono, 2 = stereo +void TDStretch::setChannels(int numChannels) +{ + assert(numChannels > 0); + if (channels == numChannels) return; + assert(numChannels == 1 || numChannels == 2); + + channels = numChannels; + inputBuffer.setChannels(channels); + outputBuffer.setChannels(channels); +} + + +// nominal tempo, no need for processing, just pass the samples through +// to outputBuffer +/* +void TDStretch::processNominalTempo() +{ + assert(tempo == 1.0f); + + if (bMidBufferDirty) + { + // If there are samples in pMidBuffer waiting for overlapping, + // do a single sliding overlapping with them in order to prevent a + // clicking distortion in the output sound + if (inputBuffer.numSamples() < overlapLength) + { + // wait until we've got overlapLength input samples + return; + } + // Mix the samples in the beginning of 'inputBuffer' with the + // samples in 'midBuffer' using sliding overlapping + overlap(outputBuffer.ptrEnd(overlapLength), inputBuffer.ptrBegin(), 0); + outputBuffer.putSamples(overlapLength); + inputBuffer.receiveSamples(overlapLength); + clearMidBuffer(); + // now we've caught the nominal sample flow and may switch to + // bypass mode + } + + // Simply bypass samples from input to output + outputBuffer.moveSamples(inputBuffer); +} +*/ + +#include + +// Processes as many processing frames of the samples 'inputBuffer', store +// the result into 'outputBuffer' +void TDStretch::processSamples() +{ + int ovlSkip, offset; + int temp; + + /* Removed this small optimization - can introduce a click to sound when tempo setting + crosses the nominal value + if (tempo == 1.0f) + { + // tempo not changed from the original, so bypass the processing + processNominalTempo(); + return; + } + */ + + // Process samples as long as there are enough samples in 'inputBuffer' + // to form a processing frame. + while ((int)inputBuffer.numSamples() >= sampleReq) + { + // If tempo differs from the normal ('SCALE'), scan for the best overlapping + // position + offset = seekBestOverlapPosition(inputBuffer.ptrBegin()); + + // Mix the samples in the 'inputBuffer' at position of 'offset' with the + // samples in 'midBuffer' using sliding overlapping + // ... first partially overlap with the end of the previous sequence + // (that's in 'midBuffer') + overlap(outputBuffer.ptrEnd((uint)overlapLength), inputBuffer.ptrBegin(), (uint)offset); + outputBuffer.putSamples((uint)overlapLength); + + // ... then copy sequence samples from 'inputBuffer' to output: + + // length of sequence + temp = (seekWindowLength - 2 * overlapLength); + + // crosscheck that we don't have buffer overflow... + if ((int)inputBuffer.numSamples() < (offset + temp + overlapLength * 2)) + { + continue; // just in case, shouldn't really happen + } + + outputBuffer.putSamples(inputBuffer.ptrBegin() + channels * (offset + overlapLength), (uint)temp); + + // Copies the end of the current sequence from 'inputBuffer' to + // 'midBuffer' for being mixed with the beginning of the next + // processing sequence and so on + assert((offset + temp + overlapLength * 2) <= (int)inputBuffer.numSamples()); + memcpy(pMidBuffer, inputBuffer.ptrBegin() + channels * (offset + temp + overlapLength), + channels * sizeof(SAMPLETYPE) * overlapLength); + + // Remove the processed samples from the input buffer. Update + // the difference between integer & nominal skip step to 'skipFract' + // in order to prevent the error from accumulating over time. + skipFract += nominalSkip; // real skip size + ovlSkip = (int)skipFract; // rounded to integer skip + skipFract -= ovlSkip; // maintain the fraction part, i.e. real vs. integer skip + inputBuffer.receiveSamples((uint)ovlSkip); + } +} + + +// Adds 'numsamples' pcs of samples from the 'samples' memory position into +// the input of the object. +void TDStretch::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + // Add the samples into the input buffer + inputBuffer.putSamples(samples, nSamples); + // Process the samples in input buffer + processSamples(); +} + + + +/// Set new overlap length parameter & reallocate RefMidBuffer if necessary. +void TDStretch::acceptNewOverlapLength(int newOverlapLength) +{ + int prevOvl; + + assert(newOverlapLength >= 0); + prevOvl = overlapLength; + overlapLength = newOverlapLength; + + if (overlapLength > prevOvl) + { + delete[] pMidBufferUnaligned; + + pMidBufferUnaligned = new SAMPLETYPE[overlapLength * 2 + 16 / sizeof(SAMPLETYPE)]; + // ensure that 'pMidBuffer' is aligned to 16 byte boundary for efficiency + pMidBuffer = (SAMPLETYPE *)((((ulong)pMidBufferUnaligned) + 15) & (ulong)-16); + + clearMidBuffer(); + } +} + + +// Operator 'new' is overloaded so that it automatically creates a suitable instance +// depending on if we've a MMX/SSE/etc-capable CPU available or not. +void * TDStretch::operator new(size_t s) +{ + // Notice! don't use "new TDStretch" directly, use "newInstance" to create a new instance instead! + ST_THROW_RT_ERROR("Error in TDStretch::new: Don't use 'new TDStretch' directly, use 'newInstance' member instead!"); + return newInstance(); +} + + +TDStretch * TDStretch::newInstance() +{ + uint uExtensions; + + uExtensions = detectCPUextensions(); + + // Check if MMX/SSE instruction set extensions supported by CPU + +#ifdef SOUNDTOUCH_ALLOW_MMX + // MMX routines available only with integer sample types + if (uExtensions & SUPPORT_MMX) + { + return ::new TDStretchMMX; + } + else +#endif // SOUNDTOUCH_ALLOW_MMX + + +#ifdef SOUNDTOUCH_ALLOW_SSE + if (uExtensions & SUPPORT_SSE) + { + // SSE support + return ::new TDStretchSSE; + } + else +#endif // SOUNDTOUCH_ALLOW_SSE + + { + // ISA optimizations not supported, use plain C version + return ::new TDStretch; + } +} + + +////////////////////////////////////////////////////////////////////////////// +// +// Integer arithmetics specific algorithm implementations. +// +////////////////////////////////////////////////////////////////////////////// + +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + +// Overlaps samples in 'midBuffer' with the samples in 'input'. The 'Stereo' +// version of the routine. +void TDStretch::overlapStereo(short *poutput, const short *input) const +{ + int i; + short temp; + int cnt2; + + for (i = 0; i < overlapLength ; i ++) + { + temp = (short)(overlapLength - i); + cnt2 = 2 * i; + poutput[cnt2] = (input[cnt2] * i + pMidBuffer[cnt2] * temp ) / overlapLength; + poutput[cnt2 + 1] = (input[cnt2 + 1] * i + pMidBuffer[cnt2 + 1] * temp ) / overlapLength; + } +} + +// Calculates the x having the closest 2^x value for the given value +static int _getClosest2Power(double value) +{ + return (int)(log(value) / log(2.0) + 0.5); +} + + +/// Calculates overlap period length in samples. +/// Integer version rounds overlap length to closest power of 2 +/// for a divide scaling operation. +void TDStretch::calculateOverlapLength(int aoverlapMs) +{ + int newOvl; + + assert(aoverlapMs >= 0); + + // calculate overlap length so that it's power of 2 - thus it's easy to do + // integer division by right-shifting. Term "-1" at end is to account for + // the extra most significatnt bit left unused in result by signed multiplication + overlapDividerBits = _getClosest2Power((sampleRate * aoverlapMs) / 1000.0) - 1; + if (overlapDividerBits > 9) overlapDividerBits = 9; + if (overlapDividerBits < 3) overlapDividerBits = 3; + newOvl = (int)pow(2.0, (int)overlapDividerBits + 1); // +1 => account for -1 above + + acceptNewOverlapLength(newOvl); + + // calculate sloping divider so that crosscorrelation operation won't + // overflow 32-bit register. Max. sum of the crosscorrelation sum without + // divider would be 2^30*(N^3-N)/3, where N = overlap length + slopingDivider = (newOvl * newOvl - 1) / 3; +} + + +double TDStretch::calcCrossCorr(const short *mixingPos, const short *compare) const +{ + long corr; + long norm; + int i; + + corr = norm = 0; + // Same routine for stereo and mono. For stereo, unroll loop for better + // efficiency and gives slightly better resolution against rounding. + // For mono it same routine, just unrolls loop by factor of 4 + for (i = 0; i < channels * overlapLength; i += 4) + { + corr += (mixingPos[i] * compare[i] + + mixingPos[i + 1] * compare[i + 1] + + mixingPos[i + 2] * compare[i + 2] + + mixingPos[i + 3] * compare[i + 3]) >> overlapDividerBits; + norm += (mixingPos[i] * mixingPos[i] + + mixingPos[i + 1] * mixingPos[i + 1] + + mixingPos[i + 2] * mixingPos[i + 2] + + mixingPos[i + 3] * mixingPos[i + 3]) >> overlapDividerBits; + } + + // Normalize result by dividing by sqrt(norm) - this step is easiest + // done using floating point operation + if (norm == 0) norm = 1; // to avoid div by zero + return (double)corr / sqrt((double)norm); +} + +#endif // SOUNDTOUCH_INTEGER_SAMPLES + +////////////////////////////////////////////////////////////////////////////// +// +// Floating point arithmetics specific algorithm implementations. +// + +#ifdef SOUNDTOUCH_FLOAT_SAMPLES + +// Overlaps samples in 'midBuffer' with the samples in 'pInput' +void TDStretch::overlapStereo(float *pOutput, const float *pInput) const +{ + int i; + float fScale; + float f1; + float f2; + + fScale = 1.0f / (float)overlapLength; + + f1 = 0; + f2 = 1.0f; + + for (i = 0; i < 2 * (int)overlapLength ; i += 2) + { + pOutput[i + 0] = pInput[i + 0] * f1 + pMidBuffer[i + 0] * f2; + pOutput[i + 1] = pInput[i + 1] * f1 + pMidBuffer[i + 1] * f2; + + f1 += fScale; + f2 -= fScale; + } +} + + +/// Calculates overlapInMsec period length in samples. +void TDStretch::calculateOverlapLength(int overlapInMsec) +{ + int newOvl; + + assert(overlapInMsec >= 0); + newOvl = (sampleRate * overlapInMsec) / 1000; + if (newOvl < 16) newOvl = 16; + + // must be divisible by 8 + newOvl -= newOvl % 8; + + acceptNewOverlapLength(newOvl); +} + + +double TDStretch::calcCrossCorr(const float *mixingPos, const float *compare) const +{ + double corr; + double norm; + int i; + + corr = norm = 0; + // Same routine for stereo and mono. For Stereo, unroll by factor of 2. + // For mono it's same routine yet unrollsd by factor of 4. + for (i = 0; i < channels * overlapLength; i += 4) + { + corr += mixingPos[i] * compare[i] + + mixingPos[i + 1] * compare[i + 1]; + + norm += mixingPos[i] * mixingPos[i] + + mixingPos[i + 1] * mixingPos[i + 1]; + + // unroll the loop for better CPU efficiency: + corr += mixingPos[i + 2] * compare[i + 2] + + mixingPos[i + 3] * compare[i + 3]; + + norm += mixingPos[i + 2] * mixingPos[i + 2] + + mixingPos[i + 3] * mixingPos[i + 3]; + } + + if (norm < 1e-9) norm = 1.0; // to avoid div by zero + return corr / sqrt(norm); +} + +#endif // SOUNDTOUCH_FLOAT_SAMPLES diff --git a/media/libsoundtouch/src/TDStretch.h b/media/libsoundtouch/src/TDStretch.h new file mode 100644 index 00000000000..fcd0767e1f9 --- /dev/null +++ b/media/libsoundtouch/src/TDStretch.h @@ -0,0 +1,268 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo +/// while maintaining the original pitch by using a time domain WSOLA-like method +/// with several performance-increasing tweaks. +/// +/// Note : MMX/SSE optimized functions reside in separate, platform-specific files +/// 'mmx_optimized.cpp' and 'sse_optimized.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: TDStretch.h 137 2012-04-01 19:49:30Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef TDStretch_H +#define TDStretch_H + +#include +#include "STTypes.h" +#include "RateTransposer.h" +#include "FIFOSamplePipe.h" + +namespace soundtouch +{ + +/// Default values for sound processing parameters: +/// Notice that the default parameters are tuned for contemporary popular music +/// processing. For speech processing applications these parameters suit better: +/// #define DEFAULT_SEQUENCE_MS 40 +/// #define DEFAULT_SEEKWINDOW_MS 15 +/// #define DEFAULT_OVERLAP_MS 8 +/// + +/// Default length of a single processing sequence, in milliseconds. This determines to how +/// long sequences the original sound is chopped in the time-stretch algorithm. +/// +/// The larger this value is, the lesser sequences are used in processing. In principle +/// a bigger value sounds better when slowing down tempo, but worse when increasing tempo +/// and vice versa. +/// +/// Increasing this value reduces computational burden & vice versa. +//#define DEFAULT_SEQUENCE_MS 40 +#define DEFAULT_SEQUENCE_MS USE_AUTO_SEQUENCE_LEN + +/// Giving this value for the sequence length sets automatic parameter value +/// according to tempo setting (recommended) +#define USE_AUTO_SEQUENCE_LEN 0 + +/// Seeking window default length in milliseconds for algorithm that finds the best possible +/// overlapping location. This determines from how wide window the algorithm may look for an +/// optimal joining location when mixing the sound sequences back together. +/// +/// The bigger this window setting is, the higher the possibility to find a better mixing +/// position will become, but at the same time large values may cause a "drifting" artifact +/// because consequent sequences will be taken at more uneven intervals. +/// +/// If there's a disturbing artifact that sounds as if a constant frequency was drifting +/// around, try reducing this setting. +/// +/// Increasing this value increases computational burden & vice versa. +//#define DEFAULT_SEEKWINDOW_MS 15 +#define DEFAULT_SEEKWINDOW_MS USE_AUTO_SEEKWINDOW_LEN + +/// Giving this value for the seek window length sets automatic parameter value +/// according to tempo setting (recommended) +#define USE_AUTO_SEEKWINDOW_LEN 0 + +/// Overlap length in milliseconds. When the chopped sound sequences are mixed back together, +/// to form a continuous sound stream, this parameter defines over how long period the two +/// consecutive sequences are let to overlap each other. +/// +/// This shouldn't be that critical parameter. If you reduce the DEFAULT_SEQUENCE_MS setting +/// by a large amount, you might wish to try a smaller value on this. +/// +/// Increasing this value increases computational burden & vice versa. +#define DEFAULT_OVERLAP_MS 8 + + +/// Class that does the time-stretch (tempo change) effect for the processed +/// sound. +class TDStretch : public FIFOProcessor +{ +protected: + int channels; + int sampleReq; + float tempo; + + SAMPLETYPE *pMidBuffer; + SAMPLETYPE *pMidBufferUnaligned; + int overlapLength; + int seekLength; + int seekWindowLength; + int overlapDividerBits; + int slopingDivider; + float nominalSkip; + float skipFract; + FIFOSampleBuffer outputBuffer; + FIFOSampleBuffer inputBuffer; + bool bQuickSeek; + + int sampleRate; + int sequenceMs; + int seekWindowMs; + int overlapMs; + bool bAutoSeqSetting; + bool bAutoSeekSetting; + + void acceptNewOverlapLength(int newOverlapLength); + + virtual void clearCrossCorrState(); + void calculateOverlapLength(int overlapMs); + + virtual double calcCrossCorr(const SAMPLETYPE *mixingPos, const SAMPLETYPE *compare) const; + + virtual int seekBestOverlapPositionFull(const SAMPLETYPE *refPos); + virtual int seekBestOverlapPositionQuick(const SAMPLETYPE *refPos); + int seekBestOverlapPosition(const SAMPLETYPE *refPos); + + virtual void overlapStereo(SAMPLETYPE *output, const SAMPLETYPE *input) const; + virtual void overlapMono(SAMPLETYPE *output, const SAMPLETYPE *input) const; + + void clearMidBuffer(); + void overlap(SAMPLETYPE *output, const SAMPLETYPE *input, uint ovlPos) const; + + void calcSeqParameters(); + + /// Changes the tempo of the given sound samples. + /// Returns amount of samples returned in the "output" buffer. + /// The maximum amount of samples that can be returned at a time is set by + /// the 'set_returnBuffer_size' function. + void processSamples(); + +public: + TDStretch(); + virtual ~TDStretch(); + + /// Operator 'new' is overloaded so that it automatically creates a suitable instance + /// depending on if we've a MMX/SSE/etc-capable CPU available or not. + static void *operator new(size_t s); + + /// Use this function instead of "new" operator to create a new instance of this class. + /// This function automatically chooses a correct feature set depending on if the CPU + /// supports MMX/SSE/etc extensions. + static TDStretch *newInstance(); + + /// Returns the output buffer object + FIFOSamplePipe *getOutput() { return &outputBuffer; }; + + /// Returns the input buffer object + FIFOSamplePipe *getInput() { return &inputBuffer; }; + + /// Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower + /// tempo, larger faster tempo. + void setTempo(float newTempo); + + /// Returns nonzero if there aren't any samples available for outputting. + virtual void clear(); + + /// Clears the input buffer + void clearInput(); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(int numChannels); + + /// Enables/disables the quick position seeking algorithm. Zero to disable, + /// nonzero to enable + void enableQuickSeek(bool enable); + + /// Returns nonzero if the quick seeking algorithm is enabled. + bool isQuickSeekEnabled() const; + + /// Sets routine control parameters. These control are certain time constants + /// defining how the sound is stretched to the desired duration. + // + /// 'sampleRate' = sample rate of the sound + /// 'sequenceMS' = one processing sequence length in milliseconds + /// 'seekwindowMS' = seeking window length for scanning the best overlapping + /// position + /// 'overlapMS' = overlapping length + void setParameters(int sampleRate, ///< Samplerate of sound being processed (Hz) + int sequenceMS = -1, ///< Single processing sequence length (ms) + int seekwindowMS = -1, ///< Offset seeking window length (ms) + int overlapMS = -1 ///< Sequence overlapping length (ms) + ); + + /// Get routine control parameters, see setParameters() function. + /// Any of the parameters to this function can be NULL, in such case corresponding parameter + /// value isn't returned. + void getParameters(int *pSampleRate, int *pSequenceMs, int *pSeekWindowMs, int *pOverlapMs) const; + + /// Adds 'numsamples' pcs of samples from the 'samples' memory position into + /// the input of the object. + virtual void putSamples( + const SAMPLETYPE *samples, ///< Input sample data + uint numSamples ///< Number of samples in 'samples' so that one sample + ///< contains both channels if stereo + ); + + /// return nominal input sample requirement for triggering a processing batch + int getInputSampleReq() const + { + return (int)(nominalSkip + 0.5); + } + + /// return nominal output sample amount when running a processing batch + int getOutputBatchSize() const + { + return seekWindowLength - overlapLength; + } +}; + + + +// Implementation-specific class declarations: + +#ifdef SOUNDTOUCH_ALLOW_MMX + /// Class that implements MMX optimized routines for 16bit integer samples type. + class TDStretchMMX : public TDStretch + { + protected: + double calcCrossCorr(const short *mixingPos, const short *compare) const; + virtual void overlapStereo(short *output, const short *input) const; + virtual void clearCrossCorrState(); + }; +#endif /// SOUNDTOUCH_ALLOW_MMX + + +#ifdef SOUNDTOUCH_ALLOW_SSE + /// Class that implements SSE optimized routines for floating point samples type. + class TDStretchSSE : public TDStretch + { + protected: + double calcCrossCorr(const float *mixingPos, const float *compare) const; + }; + +#endif /// SOUNDTOUCH_ALLOW_SSE + +} +#endif /// TDStretch_H diff --git a/media/libsoundtouch/src/cpu_detect.h b/media/libsoundtouch/src/cpu_detect.h new file mode 100644 index 00000000000..b55569060fc --- /dev/null +++ b/media/libsoundtouch/src/cpu_detect.h @@ -0,0 +1,62 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// A header file for detecting the Intel MMX instructions set extension. +/// +/// Please see 'mmx_win.cpp', 'mmx_cpp.cpp' and 'mmx_non_x86.cpp' for the +/// routine implementations for x86 Windows, x86 gnu version and non-x86 +/// platforms, respectively. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2008-02-10 08:26:55 -0800 (Sun, 10 Feb 2008) $ +// File revision : $Revision: 4 $ +// +// $Id: cpu_detect.h 11 2008-02-10 16:26:55Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _CPU_DETECT_H_ +#define _CPU_DETECT_H_ + +#include "STTypes.h" + +#define SUPPORT_MMX 0x0001 +#define SUPPORT_3DNOW 0x0002 +#define SUPPORT_ALTIVEC 0x0004 +#define SUPPORT_SSE 0x0008 +#define SUPPORT_SSE2 0x0010 + +/// Checks which instruction set extensions are supported by the CPU. +/// +/// \return A bitmask of supported extensions, see SUPPORT_... defines. +uint detectCPUextensions(void); + +/// Disables given set of instruction extensions. See SUPPORT_... defines. +void disableExtensions(uint wDisableMask); + +#endif // _CPU_DETECT_H_ diff --git a/media/libsoundtouch/src/cpu_detect_x86.cpp b/media/libsoundtouch/src/cpu_detect_x86.cpp new file mode 100644 index 00000000000..d15c6bbace6 --- /dev/null +++ b/media/libsoundtouch/src/cpu_detect_x86.cpp @@ -0,0 +1,144 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Generic version of the x86 CPU extension detection routine. +/// +/// This file is for GNU & other non-Windows compilers, see 'cpu_detect_x86_win.cpp' +/// for the Microsoft compiler version. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 13:00:09 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: cpu_detect_x86.cpp 138 2012-04-01 20:00:09Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include "cpu_detect.h" +#include "STTypes.h" + +#if defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) + #if defined(__GNUC__) + // gcc and clang + #include "cpuid.h" + #endif + + #if defined(_M_IX86) + // windows + #include + #endif + // If we still don't have the macros, define them (Windows, MacOS) + #ifndef bit_MMX + #define bit_MMX (1 << 23) + #endif + #ifndef bit_SSE + #define bit_SSE (1 << 25) + #endif + #ifndef bit_SSE2 + #define bit_SSE2 (1 << 26) + #endif +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// processor instructions extension detection routines +// +////////////////////////////////////////////////////////////////////////////// + +// Flag variable indicating whick ISA extensions are disabled (for debugging) +static uint _dwDisabledISA = 0x00; // 0xffffffff; //<- use this to disable all extensions + +// Disables given set of instruction extensions. See SUPPORT_... defines. +void disableExtensions(uint dwDisableMask) +{ + _dwDisabledISA = dwDisableMask; +} + + + +/// Checks which instruction set extensions are supported by the CPU. +uint detectCPUextensions(void) +{ +/// If building for a 64bit system (no Itanium) and the user wants optimizations. +/// Return the OR of SUPPORT_{MMX,SSE,SSE2}. 11001 or 0x19. +/// Keep the _dwDisabledISA test (2 more operations, could be eliminated). +#if ((defined(__GNUC__) && defined(__x86_64__)) \ + || defined(_M_X64)) \ + && defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) + return 0x19 & ~_dwDisabledISA; + +/// If building for a 32bit system and the user wants optimizations. +/// Keep the _dwDisabledISA test (2 more operations, could be eliminated). +#elif ((defined(__GNUC__) && defined(__i386__)) \ + || defined(_M_IX86)) \ + && defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) + + if (_dwDisabledISA == 0xffffffff) return 0; + + uint res = 0; + +#if defined(__GNUC__) + // GCC version of cpuid. Requires GCC 4.3.0 or later for __cpuid intrinsic support. + uint eax, ebx, ecx, edx; // unsigned int is the standard type. uint is defined by the compiler and not guaranteed to be portable. + + // Check if no cpuid support. + if (!__get_cpuid (1, &eax, &ebx, &ecx, &edx)) return 0; // always disable extensions. + + if (edx & bit_MMX) res = res | SUPPORT_MMX; + if (edx & bit_SSE) res = res | SUPPORT_SSE; + if (edx & bit_SSE2) res = res | SUPPORT_SSE2; + +#else + // Window / VS version of cpuid. Notice that Visual Studio 2005 or later required + // for __cpuid intrinsic support. + int reg[4] = {-1}; + + // Check if no cpuid support. + __cpuid(reg,0); + if ((unsigned int)reg[0] == 0) return 0; // always disable extensions. + + __cpuid(reg,1); + if ((unsigned int)reg[3] & bit_MMX) res = res | SUPPORT_MMX; + if ((unsigned int)reg[3] & bit_SSE) res = res | SUPPORT_SSE; + if ((unsigned int)reg[3] & bit_SSE2) res = res | SUPPORT_SSE2; + +#endif + + return res & ~_dwDisabledISA; + +#else + +/// One of these is true: +/// 1) We don't want optimizations. +/// 2) Using an unsupported compiler. +/// 3) Running on a non-x86 platform. + return 0; + +#endif +} diff --git a/media/libsoundtouch/src/mmx_optimized.cpp b/media/libsoundtouch/src/mmx_optimized.cpp new file mode 100644 index 00000000000..7cb65edd3e3 --- /dev/null +++ b/media/libsoundtouch/src/mmx_optimized.cpp @@ -0,0 +1,317 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// MMX optimized routines. All MMX optimized functions have been gathered into +/// this single source code file, regardless to their class or original source +/// code file, in order to ease porting the library to other compiler and +/// processor platforms. +/// +/// The MMX-optimizations are programmed using MMX compiler intrinsics that +/// are supported both by Microsoft Visual C++ and GCC compilers, so this file +/// should compile with both toolsets. +/// +/// NOTICE: If using Visual Studio 6.0, you'll need to install the "Visual C++ +/// 6.0 processor pack" update to support compiler intrinsic syntax. The update +/// is available for download at Microsoft Developers Network, see here: +/// http://msdn.microsoft.com/en-us/vstudio/aa718349.aspx +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: mmx_optimized.cpp 137 2012-04-01 19:49:30Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include "STTypes.h" + +#ifdef SOUNDTOUCH_ALLOW_MMX +// MMX routines available only with integer sample type + +using namespace soundtouch; + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of MMX optimized functions of class 'TDStretchMMX' +// +////////////////////////////////////////////////////////////////////////////// + +#include "TDStretch.h" +#include +#include +#include + + +// Calculates cross correlation of two buffers +double TDStretchMMX::calcCrossCorr(const short *pV1, const short *pV2) const +{ + const __m64 *pVec1, *pVec2; + __m64 shifter; + __m64 accu, normaccu; + long corr, norm; + int i; + + pVec1 = (__m64*)pV1; + pVec2 = (__m64*)pV2; + + shifter = _m_from_int(overlapDividerBits); + normaccu = accu = _mm_setzero_si64(); + + // Process 4 parallel sets of 2 * stereo samples or 4 * mono samples + // during each round for improved CPU-level parallellization. + for (i = 0; i < channels * overlapLength / 16; i ++) + { + __m64 temp, temp2; + + // dictionary of instructions: + // _m_pmaddwd : 4*16bit multiply-add, resulting two 32bits = [a0*b0+a1*b1 ; a2*b2+a3*b3] + // _mm_add_pi32 : 2*32bit add + // _m_psrad : 32bit right-shift + + temp = _mm_add_pi32(_mm_madd_pi16(pVec1[0], pVec2[0]), + _mm_madd_pi16(pVec1[1], pVec2[1])); + temp2 = _mm_add_pi32(_mm_madd_pi16(pVec1[0], pVec1[0]), + _mm_madd_pi16(pVec1[1], pVec1[1])); + accu = _mm_add_pi32(accu, _mm_sra_pi32(temp, shifter)); + normaccu = _mm_add_pi32(normaccu, _mm_sra_pi32(temp2, shifter)); + + temp = _mm_add_pi32(_mm_madd_pi16(pVec1[2], pVec2[2]), + _mm_madd_pi16(pVec1[3], pVec2[3])); + temp2 = _mm_add_pi32(_mm_madd_pi16(pVec1[2], pVec1[2]), + _mm_madd_pi16(pVec1[3], pVec1[3])); + accu = _mm_add_pi32(accu, _mm_sra_pi32(temp, shifter)); + normaccu = _mm_add_pi32(normaccu, _mm_sra_pi32(temp2, shifter)); + + pVec1 += 4; + pVec2 += 4; + } + + // copy hi-dword of mm0 to lo-dword of mm1, then sum mmo+mm1 + // and finally store the result into the variable "corr" + + accu = _mm_add_pi32(accu, _mm_srli_si64(accu, 32)); + corr = _m_to_int(accu); + + normaccu = _mm_add_pi32(normaccu, _mm_srli_si64(normaccu, 32)); + norm = _m_to_int(normaccu); + + // Clear MMS state + _m_empty(); + + // Normalize result by dividing by sqrt(norm) - this step is easiest + // done using floating point operation + if (norm == 0) norm = 1; // to avoid div by zero + + return (double)corr / sqrt((double)norm); + // Note: Warning about the missing EMMS instruction is harmless + // as it'll be called elsewhere. +} + + + +void TDStretchMMX::clearCrossCorrState() +{ + // Clear MMS state + _m_empty(); + //_asm EMMS; +} + + + +// MMX-optimized version of the function overlapStereo +void TDStretchMMX::overlapStereo(short *output, const short *input) const +{ + const __m64 *pVinput, *pVMidBuf; + __m64 *pVdest; + __m64 mix1, mix2, adder, shifter; + int i; + + pVinput = (const __m64*)input; + pVMidBuf = (const __m64*)pMidBuffer; + pVdest = (__m64*)output; + + // mix1 = mixer values for 1st stereo sample + // mix1 = mixer values for 2nd stereo sample + // adder = adder for updating mixer values after each round + + mix1 = _mm_set_pi16(0, overlapLength, 0, overlapLength); + adder = _mm_set_pi16(1, -1, 1, -1); + mix2 = _mm_add_pi16(mix1, adder); + adder = _mm_add_pi16(adder, adder); + + // Overlaplength-division by shifter. "+1" is to account for "-1" deduced in + // overlapDividerBits calculation earlier. + shifter = _m_from_int(overlapDividerBits + 1); + + for (i = 0; i < overlapLength / 4; i ++) + { + __m64 temp1, temp2; + + // load & shuffle data so that input & mixbuffer data samples are paired + temp1 = _mm_unpacklo_pi16(pVMidBuf[0], pVinput[0]); // = i0l m0l i0r m0r + temp2 = _mm_unpackhi_pi16(pVMidBuf[0], pVinput[0]); // = i1l m1l i1r m1r + + // temp = (temp .* mix) >> shifter + temp1 = _mm_sra_pi32(_mm_madd_pi16(temp1, mix1), shifter); + temp2 = _mm_sra_pi32(_mm_madd_pi16(temp2, mix2), shifter); + pVdest[0] = _mm_packs_pi32(temp1, temp2); // pack 2*2*32bit => 4*16bit + + // update mix += adder + mix1 = _mm_add_pi16(mix1, adder); + mix2 = _mm_add_pi16(mix2, adder); + + // --- second round begins here --- + + // load & shuffle data so that input & mixbuffer data samples are paired + temp1 = _mm_unpacklo_pi16(pVMidBuf[1], pVinput[1]); // = i2l m2l i2r m2r + temp2 = _mm_unpackhi_pi16(pVMidBuf[1], pVinput[1]); // = i3l m3l i3r m3r + + // temp = (temp .* mix) >> shifter + temp1 = _mm_sra_pi32(_mm_madd_pi16(temp1, mix1), shifter); + temp2 = _mm_sra_pi32(_mm_madd_pi16(temp2, mix2), shifter); + pVdest[1] = _mm_packs_pi32(temp1, temp2); // pack 2*2*32bit => 4*16bit + + // update mix += adder + mix1 = _mm_add_pi16(mix1, adder); + mix2 = _mm_add_pi16(mix2, adder); + + pVinput += 2; + pVMidBuf += 2; + pVdest += 2; + } + + _m_empty(); // clear MMS state +} + + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of MMX optimized functions of class 'FIRFilter' +// +////////////////////////////////////////////////////////////////////////////// + +#include "FIRFilter.h" + + +FIRFilterMMX::FIRFilterMMX() : FIRFilter() +{ + filterCoeffsUnalign = NULL; +} + + +FIRFilterMMX::~FIRFilterMMX() +{ + delete[] filterCoeffsUnalign; +} + + +// (overloaded) Calculates filter coefficients for MMX routine +void FIRFilterMMX::setCoefficients(const short *coeffs, uint newLength, uint uResultDivFactor) +{ + uint i; + FIRFilter::setCoefficients(coeffs, newLength, uResultDivFactor); + + // Ensure that filter coeffs array is aligned to 16-byte boundary + delete[] filterCoeffsUnalign; + filterCoeffsUnalign = new short[2 * newLength + 8]; + filterCoeffsAlign = (short *)(((ulong)filterCoeffsUnalign + 15) & -16); + + // rearrange the filter coefficients for mmx routines + for (i = 0;i < length; i += 4) + { + filterCoeffsAlign[2 * i + 0] = coeffs[i + 0]; + filterCoeffsAlign[2 * i + 1] = coeffs[i + 2]; + filterCoeffsAlign[2 * i + 2] = coeffs[i + 0]; + filterCoeffsAlign[2 * i + 3] = coeffs[i + 2]; + + filterCoeffsAlign[2 * i + 4] = coeffs[i + 1]; + filterCoeffsAlign[2 * i + 5] = coeffs[i + 3]; + filterCoeffsAlign[2 * i + 6] = coeffs[i + 1]; + filterCoeffsAlign[2 * i + 7] = coeffs[i + 3]; + } +} + + + +// mmx-optimized version of the filter routine for stereo sound +uint FIRFilterMMX::evaluateFilterStereo(short *dest, const short *src, uint numSamples) const +{ + // Create stack copies of the needed member variables for asm routines : + uint i, j; + __m64 *pVdest = (__m64*)dest; + + if (length < 2) return 0; + + for (i = 0; i < (numSamples - length) / 2; i ++) + { + __m64 accu1; + __m64 accu2; + const __m64 *pVsrc = (const __m64*)src; + const __m64 *pVfilter = (const __m64*)filterCoeffsAlign; + + accu1 = accu2 = _mm_setzero_si64(); + for (j = 0; j < lengthDiv8 * 2; j ++) + { + __m64 temp1, temp2; + + temp1 = _mm_unpacklo_pi16(pVsrc[0], pVsrc[1]); // = l2 l0 r2 r0 + temp2 = _mm_unpackhi_pi16(pVsrc[0], pVsrc[1]); // = l3 l1 r3 r1 + + accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp1, pVfilter[0])); // += l2*f2+l0*f0 r2*f2+r0*f0 + accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp2, pVfilter[1])); // += l3*f3+l1*f1 r3*f3+r1*f1 + + temp1 = _mm_unpacklo_pi16(pVsrc[1], pVsrc[2]); // = l4 l2 r4 r2 + + accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp2, pVfilter[0])); // += l3*f2+l1*f0 r3*f2+r1*f0 + accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp1, pVfilter[1])); // += l4*f3+l2*f1 r4*f3+r2*f1 + + // accu1 += l2*f2+l0*f0 r2*f2+r0*f0 + // += l3*f3+l1*f1 r3*f3+r1*f1 + + // accu2 += l3*f2+l1*f0 r3*f2+r1*f0 + // l4*f3+l2*f1 r4*f3+r2*f1 + + pVfilter += 2; + pVsrc += 2; + } + // accu >>= resultDivFactor + accu1 = _mm_srai_pi32(accu1, resultDivFactor); + accu2 = _mm_srai_pi32(accu2, resultDivFactor); + + // pack 2*2*32bits => 4*16 bits + pVdest[0] = _mm_packs_pi32(accu1, accu2); + src += 4; + pVdest ++; + } + + _m_empty(); // clear emms state + + return (numSamples & 0xfffffffe) - length; +} + +#endif // SOUNDTOUCH_ALLOW_MMX diff --git a/media/libsoundtouch/src/soundtouch.rc b/media/libsoundtouch/src/soundtouch.rc new file mode 100644 index 00000000000..313039b14bf --- /dev/null +++ b/media/libsoundtouch/src/soundtouch.rc @@ -0,0 +1,53 @@ + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +// Note: if you contain versioning information in an included +// RC script, it will be discarded +// Use module.ver to explicitly set these values + +// Do not edit this file. Changes won't affect the build. + + + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,7,0,0 + PRODUCTVERSION 1,7,0,0 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "Comments", "SoundTouch Library licensed for 3rd party applications subject to LGPL license v2.1. Visit http://www.surina.net/soundtouch for more information about the SoundTouch library." + VALUE "FileDescription", "SoundTouch Dynamic Link Library" + VALUE "FileVersion", "1, 7, 0, 0" + VALUE "InternalName", "SoundTouch" + VALUE "LegalCopyright", "Copyright (C) Olli Parviainen 1999-2012" + VALUE "OriginalFilename", "SoundTouch.dll" + VALUE "ProductName", " SoundTouch Dynamic Link Library" + VALUE "ProductVersion", "1, 7, 0, 0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END + diff --git a/media/libsoundtouch/src/soundtouch_config.h b/media/libsoundtouch/src/soundtouch_config.h new file mode 100644 index 00000000000..6807a1a21dc --- /dev/null +++ b/media/libsoundtouch/src/soundtouch_config.h @@ -0,0 +1,19 @@ +#include "mozilla/SSE.h" + +#ifdef MOZ_SAMPLE_TYPE_FLOAT32 +#define SOUNDTOUCH_FLOAT_SAMPLES 1 +#else +#define SOUNDTOUCH_INTEGER_SAMPLES 1 +#endif + +#ifndef MOZILLA_PRESUME_SSE +#ifdef MOZ_SAMPLE_TYPE_FLOAT32 +#define SOUNDTOUCH_DISABLE_X86_OPTIMIZATIONS 1 +#endif +#endif + +#ifndef MOZILLA_PRESUME_MMX +#ifdef MOZ_SAMPLE_TYPE_S16LE +#define SOUNDTOUCH_DISABLE_X86_OPTIMIZATIONS 1 +#endif +#endif diff --git a/media/libsoundtouch/src/sse_optimized.cpp b/media/libsoundtouch/src/sse_optimized.cpp new file mode 100644 index 00000000000..ea0d9b6b0a4 --- /dev/null +++ b/media/libsoundtouch/src/sse_optimized.cpp @@ -0,0 +1,361 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// SSE optimized routines for Pentium-III, Athlon-XP and later CPUs. All SSE +/// optimized functions have been gathered into this single source +/// code file, regardless to their class or original source code file, in order +/// to ease porting the library to other compiler and processor platforms. +/// +/// The SSE-optimizations are programmed using SSE compiler intrinsics that +/// are supported both by Microsoft Visual C++ and GCC compilers, so this file +/// should compile with both toolsets. +/// +/// NOTICE: If using Visual Studio 6.0, you'll need to install the "Visual C++ +/// 6.0 processor pack" update to support SSE instruction set. The update is +/// available for download at Microsoft Developers Network, see here: +/// http://msdn.microsoft.com/en-us/vstudio/aa718349.aspx +/// +/// If the above URL is expired or removed, go to "http://msdn.microsoft.com" and +/// perform a search with keywords "processor pack". +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: sse_optimized.cpp 137 2012-04-01 19:49:30Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include "cpu_detect.h" +#include "STTypes.h" + +using namespace soundtouch; + +#ifdef SOUNDTOUCH_ALLOW_SSE + +// SSE routines available only with float sample type + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of SSE optimized functions of class 'TDStretchSSE' +// +////////////////////////////////////////////////////////////////////////////// + +#include "TDStretch.h" +#include +#include + +// Calculates cross correlation of two buffers +double TDStretchSSE::calcCrossCorr(const float *pV1, const float *pV2) const +{ + int i; + const float *pVec1; + const __m128 *pVec2; + __m128 vSum, vNorm; + + // Note. It means a major slow-down if the routine needs to tolerate + // unaligned __m128 memory accesses. It's way faster if we can skip + // unaligned slots and use _mm_load_ps instruction instead of _mm_loadu_ps. + // This can mean up to ~ 10-fold difference (incl. part of which is + // due to skipping every second round for stereo sound though). + // + // Compile-time define SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION is provided + // for choosing if this little cheating is allowed. + +#ifdef SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION + // Little cheating allowed, return valid correlation only for + // aligned locations, meaning every second round for stereo sound. + + #define _MM_LOAD _mm_load_ps + + if (((ulong)pV1) & 15) return -1e50; // skip unaligned locations + +#else + // No cheating allowed, use unaligned load & take the resulting + // performance hit. + #define _MM_LOAD _mm_loadu_ps +#endif + + // ensure overlapLength is divisible by 8 + assert((overlapLength % 8) == 0); + + // Calculates the cross-correlation value between 'pV1' and 'pV2' vectors + // Note: pV2 _must_ be aligned to 16-bit boundary, pV1 need not. + pVec1 = (const float*)pV1; + pVec2 = (const __m128*)pV2; + vSum = vNorm = _mm_setzero_ps(); + + // Unroll the loop by factor of 4 * 4 operations. Use same routine for + // stereo & mono, for mono it just means twice the amount of unrolling. + for (i = 0; i < channels * overlapLength / 16; i ++) + { + __m128 vTemp; + // vSum += pV1[0..3] * pV2[0..3] + vTemp = _MM_LOAD(pVec1); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp ,pVec2[0])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + // vSum += pV1[4..7] * pV2[4..7] + vTemp = _MM_LOAD(pVec1 + 4); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[1])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + // vSum += pV1[8..11] * pV2[8..11] + vTemp = _MM_LOAD(pVec1 + 8); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[2])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + // vSum += pV1[12..15] * pV2[12..15] + vTemp = _MM_LOAD(pVec1 + 12); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[3])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + pVec1 += 16; + pVec2 += 4; + } + + // return value = vSum[0] + vSum[1] + vSum[2] + vSum[3] + float *pvNorm = (float*)&vNorm; + double norm = sqrt(pvNorm[0] + pvNorm[1] + pvNorm[2] + pvNorm[3]); + if (norm < 1e-9) norm = 1.0; // to avoid div by zero + + float *pvSum = (float*)&vSum; + return (double)(pvSum[0] + pvSum[1] + pvSum[2] + pvSum[3]) / norm; + + /* This is approximately corresponding routine in C-language yet without normalization: + double corr, norm; + uint i; + + // Calculates the cross-correlation value between 'pV1' and 'pV2' vectors + corr = norm = 0.0; + for (i = 0; i < channels * overlapLength / 16; i ++) + { + corr += pV1[0] * pV2[0] + + pV1[1] * pV2[1] + + pV1[2] * pV2[2] + + pV1[3] * pV2[3] + + pV1[4] * pV2[4] + + pV1[5] * pV2[5] + + pV1[6] * pV2[6] + + pV1[7] * pV2[7] + + pV1[8] * pV2[8] + + pV1[9] * pV2[9] + + pV1[10] * pV2[10] + + pV1[11] * pV2[11] + + pV1[12] * pV2[12] + + pV1[13] * pV2[13] + + pV1[14] * pV2[14] + + pV1[15] * pV2[15]; + + for (j = 0; j < 15; j ++) norm += pV1[j] * pV1[j]; + + pV1 += 16; + pV2 += 16; + } + return corr / sqrt(norm); + */ +} + + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of SSE optimized functions of class 'FIRFilter' +// +////////////////////////////////////////////////////////////////////////////// + +#include "FIRFilter.h" + +FIRFilterSSE::FIRFilterSSE() : FIRFilter() +{ + filterCoeffsAlign = NULL; + filterCoeffsUnalign = NULL; +} + + +FIRFilterSSE::~FIRFilterSSE() +{ + delete[] filterCoeffsUnalign; + filterCoeffsAlign = NULL; + filterCoeffsUnalign = NULL; +} + + +// (overloaded) Calculates filter coefficients for SSE routine +void FIRFilterSSE::setCoefficients(const float *coeffs, uint newLength, uint uResultDivFactor) +{ + uint i; + float fDivider; + + FIRFilter::setCoefficients(coeffs, newLength, uResultDivFactor); + + // Scale the filter coefficients so that it won't be necessary to scale the filtering result + // also rearrange coefficients suitably for SSE + // Ensure that filter coeffs array is aligned to 16-byte boundary + delete[] filterCoeffsUnalign; + filterCoeffsUnalign = new float[2 * newLength + 4]; + filterCoeffsAlign = (float *)(((unsigned long)filterCoeffsUnalign + 15) & (ulong)-16); + + fDivider = (float)resultDivider; + + // rearrange the filter coefficients for mmx routines + for (i = 0; i < newLength; i ++) + { + filterCoeffsAlign[2 * i + 0] = + filterCoeffsAlign[2 * i + 1] = coeffs[i + 0] / fDivider; + } +} + + + +// SSE-optimized version of the filter routine for stereo sound +uint FIRFilterSSE::evaluateFilterStereo(float *dest, const float *source, uint numSamples) const +{ + int count = (int)((numSamples - length) & (uint)-2); + int j; + + assert(count % 2 == 0); + + if (count < 2) return 0; + + assert(source != NULL); + assert(dest != NULL); + assert((length % 8) == 0); + assert(filterCoeffsAlign != NULL); + assert(((ulong)filterCoeffsAlign) % 16 == 0); + + // filter is evaluated for two stereo samples with each iteration, thus use of 'j += 2' + for (j = 0; j < count; j += 2) + { + const float *pSrc; + const __m128 *pFil; + __m128 sum1, sum2; + uint i; + + pSrc = (const float*)source; // source audio data + pFil = (const __m128*)filterCoeffsAlign; // filter coefficients. NOTE: Assumes coefficients + // are aligned to 16-byte boundary + sum1 = sum2 = _mm_setzero_ps(); + + for (i = 0; i < length / 8; i ++) + { + // Unroll loop for efficiency & calculate filter for 2*2 stereo samples + // at each pass + + // sum1 is accu for 2*2 filtered stereo sound data at the primary sound data offset + // sum2 is accu for 2*2 filtered stereo sound data for the next sound sample offset. + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc) , pFil[0])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 2), pFil[0])); + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 4), pFil[1])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 6), pFil[1])); + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 8) , pFil[2])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 10), pFil[2])); + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 12), pFil[3])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 14), pFil[3])); + + pSrc += 16; + pFil += 4; + } + + // Now sum1 and sum2 both have a filtered 2-channel sample each, but we still need + // to sum the two hi- and lo-floats of these registers together. + + // post-shuffle & add the filtered values and store to dest. + _mm_storeu_ps(dest, _mm_add_ps( + _mm_shuffle_ps(sum1, sum2, _MM_SHUFFLE(1,0,3,2)), // s2_1 s2_0 s1_3 s1_2 + _mm_shuffle_ps(sum1, sum2, _MM_SHUFFLE(3,2,1,0)) // s2_3 s2_2 s1_1 s1_0 + )); + source += 4; + dest += 4; + } + + // Ideas for further improvement: + // 1. If it could be guaranteed that 'source' were always aligned to 16-byte + // boundary, a faster aligned '_mm_load_ps' instruction could be used. + // 2. If it could be guaranteed that 'dest' were always aligned to 16-byte + // boundary, a faster '_mm_store_ps' instruction could be used. + + return (uint)count; + + /* original routine in C-language. please notice the C-version has differently + organized coefficients though. + double suml1, suml2; + double sumr1, sumr2; + uint i, j; + + for (j = 0; j < count; j += 2) + { + const float *ptr; + const float *pFil; + + suml1 = sumr1 = 0.0; + suml2 = sumr2 = 0.0; + ptr = src; + pFil = filterCoeffs; + for (i = 0; i < lengthLocal; i ++) + { + // unroll loop for efficiency. + + suml1 += ptr[0] * pFil[0] + + ptr[2] * pFil[2] + + ptr[4] * pFil[4] + + ptr[6] * pFil[6]; + + sumr1 += ptr[1] * pFil[1] + + ptr[3] * pFil[3] + + ptr[5] * pFil[5] + + ptr[7] * pFil[7]; + + suml2 += ptr[8] * pFil[0] + + ptr[10] * pFil[2] + + ptr[12] * pFil[4] + + ptr[14] * pFil[6]; + + sumr2 += ptr[9] * pFil[1] + + ptr[11] * pFil[3] + + ptr[13] * pFil[5] + + ptr[15] * pFil[7]; + + ptr += 16; + pFil += 8; + } + dest[0] = (float)suml1; + dest[1] = (float)sumr1; + dest[2] = (float)suml2; + dest[3] = (float)sumr2; + + src += 4; + dest += 4; + } + */ +} + +#endif // SOUNDTOUCH_ALLOW_SSE diff --git a/media/libsoundtouch/update.sh b/media/libsoundtouch/update.sh new file mode 100644 index 00000000000..9940601bcd0 --- /dev/null +++ b/media/libsoundtouch/update.sh @@ -0,0 +1,40 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Usage: ./update.sh +# +# Copies the needed files from a directory containing the original +# soundtouch sources that we need for HTML5 media playback rate change. + +cp $1/COPYING.TXT . +cp $1/source/SoundTouch/AAFilter.cpp src +cp $1/source/SoundTouch/AAFilter.h src +cp $1/source/SoundTouch/cpu_detect.h src +cp $1/source/SoundTouch/cpu_detect_x86.cpp src +cp $1/source/SoundTouch/FIFOSampleBuffer.cpp src +cp $1/source/SoundTouch/FIRFilter.cpp src +cp $1/source/SoundTouch/FIRFilter.h src +cp $1/source/SoundTouch/mmx_optimized.cpp src +cp $1/source/SoundTouch/RateTransposer.cpp src +cp $1/source/SoundTouch/RateTransposer.h src +cp $1/source/SoundTouch/SoundTouch.cpp src +cp $1/source/SoundTouch/sse_optimized.cpp src +cp $1/source/SoundTouch/TDStretch.cpp src +cp $1/source/SoundTouch/TDStretch.h src +cp $1/include/SoundTouch.h src +cp $1/include/FIFOSampleBuffer.h src +cp $1/include/FIFOSamplePipe.h src +cp $1/include/SoundTouch.h src +cp $1/include/STTypes.h src + +# Remote the Windows line ending characters from the files. +for i in src/* +do + cat $i | tr -d '\015' > $i.lf + mv $i.lf $i +done + +# Patch the imported files. +patch -p1 < moz-libsoundtouch.patch + diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 7253073b50e..9d7ed11b58d 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -40,6 +40,7 @@ @BINPATH@/@DLL_PREFIX@xpcom@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@omxplugin@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@xul@DLL_SUFFIX@ diff --git a/mobile/xul/installer/package-manifest.in b/mobile/xul/installer/package-manifest.in index 8b0fd4892d8..94ea7af4bec 100644 --- a/mobile/xul/installer/package-manifest.in +++ b/mobile/xul/installer/package-manifest.in @@ -51,6 +51,7 @@ @BINPATH@/@DLL_PREFIX@xpcom@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@omxplugin@DLL_SUFFIX@ #ifdef XP_MACOSX diff --git a/toolkit/content/license.html b/toolkit/content/license.html index b2b4a1b881b..e1888afb612 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -87,6 +87,7 @@
  • libffi License
  • libjingle License
  • libnestegg License
  • +
  • libsoundtouch License
  • libsrtp License
  • libunwind License
  • libyuv License
  • @@ -1967,6 +1968,32 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    +

    libsoundtouch License

    + +

    This license applies to certain files in the directory + media/libsoundtouch/src/. +

    + +
    +The SoundTouch Library Copyright © Olli Parviainen 2001-2012
    +
    +This library is free software; you can redistribute it and/or
    +modify it under the terms of the GNU Lesser General Public
    +License as published by the Free Software Foundation; either
    +version 2.1 of the License, or (at your option) any later version.
    +
    +This library is distributed in the hope that it will be useful,
    +but WITHOUT ANY WARRANTY; without even the implied warranty of
    +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    +Lesser General Public License for more details.
    +
    +You should have received a copy of the GNU Lesser General Public
    +License along with this library; if not, write to the Free Software
    +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
    +
    + +
    +

    libsrtp License

    This license applies to files in the directory diff --git a/toolkit/library/Makefile.in b/toolkit/library/Makefile.in index 8ddeb2cb1f6..677f4540806 100644 --- a/toolkit/library/Makefile.in +++ b/toolkit/library/Makefile.in @@ -358,6 +358,7 @@ EXTRA_DSO_LDOPTS += \ $(MOZ_CAIRO_OSLIBS) \ $(MOZ_APP_EXTRA_LIBS) \ $(SQLITE_LIBS) \ + $(SOUNDTOUCH_LIBS) \ $(NULL) ifdef MOZ_NATIVE_JPEG diff --git a/toolkit/mozapps/installer/packager.mk b/toolkit/mozapps/installer/packager.mk index 6da907a07ef..dd63f6de6b9 100644 --- a/toolkit/mozapps/installer/packager.mk +++ b/toolkit/mozapps/installer/packager.mk @@ -262,6 +262,7 @@ DIST_FILES += \ libplc4.so \ libplds4.so \ libmozsqlite3.so \ + libsoundtouch.so \ libnssutil3.so \ libnss3.so \ libssl3.so \ diff --git a/toolkit/toolkit-makefiles.sh b/toolkit/toolkit-makefiles.sh index 91d25475aee..5ae3a99c9d9 100644 --- a/toolkit/toolkit-makefiles.sh +++ b/toolkit/toolkit-makefiles.sh @@ -1737,3 +1737,11 @@ if [ "$MOZ_SPEEX_RESAMPLER" ]; then media/libspeex_resampler/src/Makefile " fi + +if [ "$MOZ_SOUNDTOUCH" ]; then + add_makefiles " + media/libsoundtouch/Makefile + media/libsoundtouch/src/Makefile + " +fi + diff --git a/toolkit/toolkit-tiers.mk b/toolkit/toolkit-tiers.mk index 4b16d2380c1..aecd4f56e69 100644 --- a/toolkit/toolkit-tiers.mk +++ b/toolkit/toolkit-tiers.mk @@ -151,6 +151,12 @@ tier_platform_dirs += \ $(NULL) endif +ifdef MOZ_SOUNDTOUCH +tier_platform_dirs += \ + media/libsoundtouch \ + $(NULL) +endif + ifdef MOZ_CUBEB tier_platform_dirs += \ media/libcubeb \ From 63623db352202e0cb32f7f27894aa9134a0e5749 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Wed, 7 Nov 2012 10:30:49 +0100 Subject: [PATCH 09/77] Backed out changeset 98f9d1044e54 because r= bit is missing in commit message. --- b2g/installer/package-manifest.in | 1 - browser/installer/package-manifest.in | 1 - config/system-headers | 3 +- configure.in | 15 - js/src/config/system-headers | 3 +- media/libsoundtouch/AUTHORS | 4 - media/libsoundtouch/LICENSE | 458 ----------- media/libsoundtouch/Makefile.in | 17 - media/libsoundtouch/README_MOZILLA | 8 - media/libsoundtouch/moz-libsoundtouch.patch | 509 ------------ media/libsoundtouch/src/AAFilter.cpp | 184 ----- media/libsoundtouch/src/AAFilter.h | 91 --- media/libsoundtouch/src/FIFOSampleBuffer.cpp | 274 ------- media/libsoundtouch/src/FIFOSampleBuffer.h | 178 ---- media/libsoundtouch/src/FIFOSamplePipe.h | 234 ------ media/libsoundtouch/src/FIRFilter.cpp | 259 ------ media/libsoundtouch/src/FIRFilter.h | 145 ---- media/libsoundtouch/src/Makefile.in | 69 -- media/libsoundtouch/src/RateTransposer.cpp | 626 -------------- media/libsoundtouch/src/RateTransposer.h | 159 ---- media/libsoundtouch/src/STTypes.h | 159 ---- media/libsoundtouch/src/SoundTouch.cpp | 501 ------------ media/libsoundtouch/src/SoundTouch.h | 277 ------- media/libsoundtouch/src/TDStretch.cpp | 808 ------------------- media/libsoundtouch/src/TDStretch.h | 268 ------ media/libsoundtouch/src/cpu_detect.h | 62 -- media/libsoundtouch/src/cpu_detect_x86.cpp | 144 ---- media/libsoundtouch/src/mmx_optimized.cpp | 317 -------- media/libsoundtouch/src/soundtouch.rc | 53 -- media/libsoundtouch/src/soundtouch_config.h | 19 - media/libsoundtouch/src/sse_optimized.cpp | 361 --------- media/libsoundtouch/update.sh | 40 - mobile/android/installer/package-manifest.in | 1 - mobile/xul/installer/package-manifest.in | 1 - toolkit/content/license.html | 27 - toolkit/library/Makefile.in | 1 - toolkit/mozapps/installer/packager.mk | 1 - toolkit/toolkit-makefiles.sh | 8 - toolkit/toolkit-tiers.mk | 6 - 39 files changed, 2 insertions(+), 6290 deletions(-) delete mode 100644 media/libsoundtouch/AUTHORS delete mode 100644 media/libsoundtouch/LICENSE delete mode 100644 media/libsoundtouch/Makefile.in delete mode 100644 media/libsoundtouch/README_MOZILLA delete mode 100644 media/libsoundtouch/moz-libsoundtouch.patch delete mode 100644 media/libsoundtouch/src/AAFilter.cpp delete mode 100644 media/libsoundtouch/src/AAFilter.h delete mode 100644 media/libsoundtouch/src/FIFOSampleBuffer.cpp delete mode 100644 media/libsoundtouch/src/FIFOSampleBuffer.h delete mode 100644 media/libsoundtouch/src/FIFOSamplePipe.h delete mode 100644 media/libsoundtouch/src/FIRFilter.cpp delete mode 100644 media/libsoundtouch/src/FIRFilter.h delete mode 100644 media/libsoundtouch/src/Makefile.in delete mode 100644 media/libsoundtouch/src/RateTransposer.cpp delete mode 100644 media/libsoundtouch/src/RateTransposer.h delete mode 100644 media/libsoundtouch/src/STTypes.h delete mode 100644 media/libsoundtouch/src/SoundTouch.cpp delete mode 100644 media/libsoundtouch/src/SoundTouch.h delete mode 100644 media/libsoundtouch/src/TDStretch.cpp delete mode 100644 media/libsoundtouch/src/TDStretch.h delete mode 100644 media/libsoundtouch/src/cpu_detect.h delete mode 100644 media/libsoundtouch/src/cpu_detect_x86.cpp delete mode 100644 media/libsoundtouch/src/mmx_optimized.cpp delete mode 100644 media/libsoundtouch/src/soundtouch.rc delete mode 100644 media/libsoundtouch/src/soundtouch_config.h delete mode 100644 media/libsoundtouch/src/sse_optimized.cpp delete mode 100644 media/libsoundtouch/update.sh diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 8687a1eed2b..9399b762c81 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -54,7 +54,6 @@ @BINPATH@/@DLL_PREFIX@xpcom@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ -@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ #ifdef XP_MACOSX @BINPATH@/XUL #else diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 266b49dba0c..52f3f4be673 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -54,7 +54,6 @@ @BINPATH@/@DLL_PREFIX@gkmedias@DLL_SUFFIX@ #endif @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ -@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ #ifdef MOZ_SHARED_MOZGLUE @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@ #endif diff --git a/config/system-headers b/config/system-headers index 74fc247fdb0..f781c8220d0 100644 --- a/config/system-headers +++ b/config/system-headers @@ -981,8 +981,6 @@ plstr.h plarenas.h plarena.h plhash.h -speex/speex_resampler.h -soundtouch/SoundTouch.h #if MOZ_NATIVE_PNG==1 png.h #endif @@ -1055,6 +1053,7 @@ vpx/vpx_encoder.h vpx/vp8cx.h vpx/vp8dx.h sydneyaudio/sydney_audio.h +speex/speex_resampler.h vorbis/codec.h theora/theoradec.h tremor/ivorbiscodec.h diff --git a/configure.in b/configure.in index 5bb372e5a02..ef2acbc7a85 100644 --- a/configure.in +++ b/configure.in @@ -4225,7 +4225,6 @@ MOZ_OGG=1 MOZ_RAW= MOZ_SYDNEYAUDIO= MOZ_SPEEX_RESAMPLER=1 -MOZ_SOUNDTOUCH=1 MOZ_CUBEB= MOZ_VORBIS= MOZ_TREMOR= @@ -5581,19 +5580,6 @@ if test -n "$MOZ_SPEEX_RESAMPLER"; then AC_DEFINE(MOZ_SPEEX_RESAMPLER) fi -if test -n "$MOZ_SOUNDTOUCH"; then - AC_DEFINE(MOZ_SOUNDTOUCH) -fi - -if test -z "$GNU_CC" -a "$OS_ARCH" = "WINNT"; then - SOUNDTOUCH_LIBS='$(LIBXUL_DIST)/lib/$(LIB_PREFIX)soundtouch.$(LIB_SUFFIX)' -else - SOUNDTOUCH_LIBS='-lsoundtouch' -fi -AC_SUBST(SOUNDTOUCH_CFLAGS) -AC_SUBST(SOUNDTOUCH_LIBS) -AC_SUBST(SOUNDTOUCH_CONFIG) - if test -n "$MOZ_CUBEB"; then case "$target" in *-android*|*-linuxandroid*) @@ -8667,7 +8653,6 @@ AC_SUBST(MOZ_APP_EXTRA_LIBS) AC_SUBST(MOZ_MEDIA) AC_SUBST(MOZ_SYDNEYAUDIO) AC_SUBST(MOZ_SPEEX_RESAMPLER) -AC_SUBST(MOZ_SOUNDTOUCH) AC_SUBST(MOZ_CUBEB) AC_SUBST(MOZ_WAVE) AC_SUBST(MOZ_VORBIS) diff --git a/js/src/config/system-headers b/js/src/config/system-headers index 74fc247fdb0..f781c8220d0 100644 --- a/js/src/config/system-headers +++ b/js/src/config/system-headers @@ -981,8 +981,6 @@ plstr.h plarenas.h plarena.h plhash.h -speex/speex_resampler.h -soundtouch/SoundTouch.h #if MOZ_NATIVE_PNG==1 png.h #endif @@ -1055,6 +1053,7 @@ vpx/vpx_encoder.h vpx/vp8cx.h vpx/vp8dx.h sydneyaudio/sydney_audio.h +speex/speex_resampler.h vorbis/codec.h theora/theoradec.h tremor/ivorbiscodec.h diff --git a/media/libsoundtouch/AUTHORS b/media/libsoundtouch/AUTHORS deleted file mode 100644 index 666081e1952..00000000000 --- a/media/libsoundtouch/AUTHORS +++ /dev/null @@ -1,4 +0,0 @@ -The SoundTouch Library -Copyright © Olli Parviainen 2001-2012 - -http://www.surina.net/soundtouch/ diff --git a/media/libsoundtouch/LICENSE b/media/libsoundtouch/LICENSE deleted file mode 100644 index 5b2161be204..00000000000 --- a/media/libsoundtouch/LICENSE +++ /dev/null @@ -1,458 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authoried party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS diff --git a/media/libsoundtouch/Makefile.in b/media/libsoundtouch/Makefile.in deleted file mode 100644 index a8c74390b16..00000000000 --- a/media/libsoundtouch/Makefile.in +++ /dev/null @@ -1,17 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -DEPTH = ../.. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -MODULE = soundtouch - -DIRS = src \ - $(NULL) - -include $(topsrcdir)/config/rules.mk diff --git a/media/libsoundtouch/README_MOZILLA b/media/libsoundtouch/README_MOZILLA deleted file mode 100644 index 141860babd4..00000000000 --- a/media/libsoundtouch/README_MOZILLA +++ /dev/null @@ -1,8 +0,0 @@ -These files are from the SoundTouch library (http://www.surina.net/soundtouch/), -and are extracted from the revision r143 of the svn repository at -https://soundtouch.svn.sourceforge.net/svnroot/soundtouch/trunk. - -The whole library is not used, only the relevant files are imported in the tree, -using the script `update.sh`. Some changes have been made to the files, using -the patch `moz-libsoundtouch.patch`. We also use a custom soundtouch_config.h. - diff --git a/media/libsoundtouch/moz-libsoundtouch.patch b/media/libsoundtouch/moz-libsoundtouch.patch deleted file mode 100644 index f78ec818561..00000000000 --- a/media/libsoundtouch/moz-libsoundtouch.patch +++ /dev/null @@ -1,509 +0,0 @@ -unchanged: ---- /src/STTypes.h 2012-08-02 10:04:06.301691592 -0700 -+++ /src/STTypes.h -@@ -42,21 +42,13 @@ - typedef unsigned int uint; - typedef unsigned long ulong; - --#ifdef __GNUC__ -- // In GCC, include soundtouch_config.h made by config scritps -- #include "soundtouch_config.h" --#endif -- --#ifndef _WINDEF_ -- // if these aren't defined already by Windows headers, define now -- -- typedef int BOOL; -- -- #define FALSE 0 -- #define TRUE 1 -- --#endif // _WINDEF_ -+#include "soundtouch_config.h" - -+#ifdef WIN32 -+#define EXPORT __declspec(dllexport) -+#else -+#define EXPORT -+#endif - - namespace soundtouch - { -@@ -82,7 +74,7 @@ - /// also in GNU environment, then please #undef the INTEGER_SAMPLE - /// and FLOAT_SAMPLE defines first as in comments above. - //#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples -- #define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples -+ #define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples - - #endif - -@@ -144,10 +136,10 @@ - - #endif // SOUNDTOUCH_INTEGER_SAMPLES - --}; -+} - - // define ST_NO_EXCEPTION_HANDLING switch to disable throwing std exceptions: --// #define ST_NO_EXCEPTION_HANDLING 1 -+#define ST_NO_EXCEPTION_HANDLING 1 - #ifdef ST_NO_EXCEPTION_HANDLING - // Exceptions disabled. Throw asserts instead if enabled. - #include ---- /src/SoundTouch.h 2012-08-02 10:04:06.301691592 -0700 -+++ /src/SoundTouch.h -@@ -141,7 +141,7 @@ - /// tempo/pitch/rate/samplerate settings. - #define SETTING_NOMINAL_OUTPUT_SEQUENCE 7 - --class SoundTouch : public FIFOProcessor -+class EXPORT SoundTouch : public FIFOProcessor - { - private: - /// Rate transposer class instance -@@ -160,7 +160,7 @@ - float virtualPitch; - - /// Flag: Has sample rate been set? -- BOOL bSrateSet; -+ bool bSrateSet; - - /// Calculates effective rate & tempo valuescfrom 'virtualRate', 'virtualTempo' and - /// 'virtualPitch' parameters. -@@ -247,8 +247,8 @@ - /// Changes a setting controlling the processing system behaviour. See the - /// 'SETTING_...' defines for available setting ID's. - /// -- /// \return 'TRUE' if the setting was succesfully changed -- BOOL setSetting(int settingId, ///< Setting ID number. see SETTING_... defines. -+ /// \return 'true' if the setting was succesfully changed -+ bool setSetting(int settingId, ///< Setting ID number. see SETTING_... defines. - int value ///< New setting value. - ); - ---- /src/RateTransposer.cpp -+++ /src/RateTransposer.cpp -@@ -120,17 +120,17 @@ RateTransposer *RateTransposer::newInsta - #endif - } - - - // Constructor - RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer) - { - numChannels = 2; -- bUseAAFilter = TRUE; -+ bUseAAFilter = true; - fRate = 0; - - // Instantiates the anti-alias filter with default tap length - // of 32 - pAAFilter = new AAFilter(32); - } - - -@@ -138,24 +138,24 @@ RateTransposer::RateTransposer() : FIFOP - RateTransposer::~RateTransposer() - { - delete pAAFilter; - } - - - - /// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable --void RateTransposer::enableAAFilter(BOOL newMode) -+void RateTransposer::enableAAFilter(bool newMode) - { - bUseAAFilter = newMode; - } - - - /// Returns nonzero if anti-alias filter is enabled. --BOOL RateTransposer::isAAFilterEnabled() const -+bool RateTransposer::isAAFilterEnabled() const - { - return bUseAAFilter; - } - - - AAFilter *RateTransposer::getAAFilter() - { - return pAAFilter; -@@ -281,17 +281,17 @@ void RateTransposer::processSamples(cons - uint count; - uint sizeReq; - - if (nSamples == 0) return; - assert(pAAFilter); - - // If anti-alias filter is turned off, simply transpose without applying - // the filter -- if (bUseAAFilter == FALSE) -+ if (bUseAAFilter == false) - { - sizeReq = (uint)((float)nSamples / fRate + 1.0f); - count = transpose(outputBuffer.ptrEnd(sizeReq), src, nSamples); - outputBuffer.putSamples(count); - return; - } - - // Transpose with anti-alias filter ---- /src/RateTransposer.h -+++ /src/RateTransposer.h -@@ -76,17 +76,17 @@ protected: - FIFOSampleBuffer storeBuffer; - - /// Buffer for keeping samples between transposing & anti-alias filter - FIFOSampleBuffer tempBuffer; - - /// Output sample buffer - FIFOSampleBuffer outputBuffer; - -- BOOL bUseAAFilter; -+ bool bUseAAFilter; - - virtual void resetRegisters() = 0; - - virtual uint transposeStereo(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples) = 0; - virtual uint transposeMono(SAMPLETYPE *dest, - const SAMPLETYPE *src, -@@ -126,20 +126,20 @@ public: - - /// Returns the store buffer object - FIFOSamplePipe *getStore() { return &storeBuffer; }; - - /// Return anti-alias filter object - AAFilter *getAAFilter(); - - /// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable -- void enableAAFilter(BOOL newMode); -+ void enableAAFilter(bool newMode); - - /// Returns nonzero if anti-alias filter is enabled. -- BOOL isAAFilterEnabled() const; -+ bool isAAFilterEnabled() const; - - /// Sets new target rate. Normal rate = 1.0, smaller values represent slower - /// rate, larger faster rates. - virtual void setRate(float newRate); - - /// Sets the number of channels, 1 = mono, 2 = stereo - void setChannels(int channels); - ---- /src/SoundTouch.cpp -+++ /src/SoundTouch.cpp -@@ -106,17 +106,17 @@ SoundTouch::SoundTouch() - - virtualPitch = - virtualRate = - virtualTempo = 1.0; - - calcEffectiveRateAndTempo(); - - channels = 0; -- bSrateSet = FALSE; -+ bSrateSet = false; - } - - - - SoundTouch::~SoundTouch() - { - delete pRateTransposer; - delete pTDStretch; -@@ -277,27 +277,27 @@ void SoundTouch::calcEffectiveRateAndTem - } - } - } - - - // Sets sample rate. - void SoundTouch::setSampleRate(uint srate) - { -- bSrateSet = TRUE; -+ bSrateSet = true; - // set sample rate, leave other tempo changer parameters as they are. - pTDStretch->setParameters((int)srate); - } - - - // Adds 'numSamples' pcs of samples from the 'samples' memory position into - // the input of the object. - void SoundTouch::putSamples(const SAMPLETYPE *samples, uint nSamples) - { -- if (bSrateSet == FALSE) -+ if (bSrateSet == false) - { - ST_THROW_RT_ERROR("SoundTouch : Sample rate not defined"); - } - else if (channels == 0) - { - ST_THROW_RT_ERROR("SoundTouch : Number of channels not defined"); - } - -@@ -382,57 +382,57 @@ void SoundTouch::flush() - pTDStretch->clearInput(); - // yet leave the 'tempoChanger' output intouched as that's where the - // flushed samples are! - } - - - // Changes a setting controlling the processing system behaviour. See the - // 'SETTING_...' defines for available setting ID's. --BOOL SoundTouch::setSetting(int settingId, int value) -+bool SoundTouch::setSetting(int settingId, int value) - { - int sampleRate, sequenceMs, seekWindowMs, overlapMs; - - // read current tdstretch routine parameters - pTDStretch->getParameters(&sampleRate, &sequenceMs, &seekWindowMs, &overlapMs); - - switch (settingId) - { - case SETTING_USE_AA_FILTER : - // enables / disabless anti-alias filter -- pRateTransposer->enableAAFilter((value != 0) ? TRUE : FALSE); -- return TRUE; -+ pRateTransposer->enableAAFilter((value != 0) ? true : false); -+ return true; - - case SETTING_AA_FILTER_LENGTH : - // sets anti-alias filter length - pRateTransposer->getAAFilter()->setLength(value); -- return TRUE; -+ return true; - - case SETTING_USE_QUICKSEEK : - // enables / disables tempo routine quick seeking algorithm -- pTDStretch->enableQuickSeek((value != 0) ? TRUE : FALSE); -- return TRUE; -+ pTDStretch->enableQuickSeek((value != 0) ? true : false); -+ return true; - - case SETTING_SEQUENCE_MS: - // change time-stretch sequence duration parameter - pTDStretch->setParameters(sampleRate, value, seekWindowMs, overlapMs); -- return TRUE; -+ return true; - - case SETTING_SEEKWINDOW_MS: - // change time-stretch seek window length parameter - pTDStretch->setParameters(sampleRate, sequenceMs, value, overlapMs); -- return TRUE; -+ return true; - - case SETTING_OVERLAP_MS: - // change time-stretch overlap length parameter - pTDStretch->setParameters(sampleRate, sequenceMs, seekWindowMs, value); -- return TRUE; -+ return true; - - default : -- return FALSE; -+ return false; - } - } - - - // Reads a setting controlling the processing system behaviour. See the - // 'SETTING_...' defines for available setting ID's. - // - // Returns the setting value. ---- /src/TDStretch.cpp -+++ /src/TDStretch.cpp -@@ -81,25 +81,25 @@ static const short _scanOffsets[5][24]={ - * - * Implementation of the class 'TDStretch' - * - *****************************************************************************/ - - - TDStretch::TDStretch() : FIFOProcessor(&outputBuffer) - { -- bQuickSeek = FALSE; -+ bQuickSeek = false; - channels = 2; - - pMidBuffer = NULL; - pMidBufferUnaligned = NULL; - overlapLength = 0; - -- bAutoSeqSetting = TRUE; -- bAutoSeekSetting = TRUE; -+ bAutoSeqSetting = true; -+ bAutoSeekSetting = true; - - // outDebt = 0; - skipFract = 0; - - tempo = 1.0f; - setParameters(44100, DEFAULT_SEQUENCE_MS, DEFAULT_SEEKWINDOW_MS, DEFAULT_OVERLAP_MS); - setTempo(1.0f); - -@@ -129,33 +129,33 @@ void TDStretch::setParameters(int aSampl - { - // accept only positive parameter values - if zero or negative, use old values instead - if (aSampleRate > 0) this->sampleRate = aSampleRate; - if (aOverlapMS > 0) this->overlapMs = aOverlapMS; - - if (aSequenceMS > 0) - { - this->sequenceMs = aSequenceMS; -- bAutoSeqSetting = FALSE; -+ bAutoSeqSetting = false; - } - else if (aSequenceMS == 0) - { - // if zero, use automatic setting -- bAutoSeqSetting = TRUE; -+ bAutoSeqSetting = true; - } - - if (aSeekWindowMS > 0) - { - this->seekWindowMs = aSeekWindowMS; -- bAutoSeekSetting = FALSE; -+ bAutoSeekSetting = false; - } - else if (aSeekWindowMS == 0) - { - // if zero, use automatic setting -- bAutoSeekSetting = TRUE; -+ bAutoSeekSetting = true; - } - - calcSeqParameters(); - - calculateOverlapLength(overlapMs); - - // set tempo to recalculate 'sampleReq' - setTempo(tempo); -@@ -229,24 +229,24 @@ void TDStretch::clear() - outputBuffer.clear(); - clearInput(); - } - - - - // Enables/disables the quick position seeking algorithm. Zero to disable, nonzero - // to enable --void TDStretch::enableQuickSeek(BOOL enable) -+void TDStretch::enableQuickSeek(bool enable) - { - bQuickSeek = enable; - } - - - // Returns nonzero if the quick seeking algorithm is enabled. --BOOL TDStretch::isQuickSeekEnabled() const -+bool TDStretch::isQuickSeekEnabled() const - { - return bQuickSeek; - } - - - // Seeks for the optimal overlap-mixing position. - int TDStretch::seekBestOverlapPosition(const SAMPLETYPE *refPos) - { ---- /src/TDStretch.h -+++ /src/TDStretch.h -@@ -120,24 +120,24 @@ protected: - int seekLength; - int seekWindowLength; - int overlapDividerBits; - int slopingDivider; - float nominalSkip; - float skipFract; - FIFOSampleBuffer outputBuffer; - FIFOSampleBuffer inputBuffer; -- BOOL bQuickSeek; -+ bool bQuickSeek; - - int sampleRate; - int sequenceMs; - int seekWindowMs; - int overlapMs; -- BOOL bAutoSeqSetting; -- BOOL bAutoSeekSetting; -+ bool bAutoSeqSetting; -+ bool bAutoSeekSetting; - - void acceptNewOverlapLength(int newOverlapLength); - - virtual void clearCrossCorrState(); - void calculateOverlapLength(int overlapMs); - - virtual double calcCrossCorr(const SAMPLETYPE *mixingPos, const SAMPLETYPE *compare) const; - -@@ -188,20 +188,20 @@ public: - /// Clears the input buffer - void clearInput(); - - /// Sets the number of channels, 1 = mono, 2 = stereo - void setChannels(int numChannels); - - /// Enables/disables the quick position seeking algorithm. Zero to disable, - /// nonzero to enable -- void enableQuickSeek(BOOL enable); -+ void enableQuickSeek(bool enable); - - /// Returns nonzero if the quick seeking algorithm is enabled. -- BOOL isQuickSeekEnabled() const; -+ bool isQuickSeekEnabled() const; - - /// Sets routine control parameters. These control are certain time constants - /// defining how the sound is stretched to the desired duration. - // - /// 'sampleRate' = sample rate of the sound - /// 'sequenceMS' = one processing sequence length in milliseconds - /// 'seekwindowMS' = seeking window length for scanning the best overlapping - /// position -only in patch2: ---- /src/cpu_detect_x86.cpp 2012-04-12 19:52:12.743376976 -0700 -+++ /src/cpu_detect_x86.cpp 2012-08-02 09:54:24.561712171 -0700 -@@ -38,30 +38,35 @@ - // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - // - //////////////////////////////////////////////////////////////////////////////// - - #include "cpu_detect.h" - #include "STTypes.h" - - #if defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) -- -- #if defined(__GNUC__) && defined(__i386__) -- // gcc -+ #if defined(__GNUC__) -+ // gcc and clang - #include "cpuid.h" - #endif - - #if defined(_M_IX86) - // windows - #include -- #define bit_MMX (1 << 23) -- #define bit_SSE (1 << 25) -- #define bit_SSE2 (1 << 26) - #endif -- -+ // If we still don't have the macros, define them (Windows, MacOS) -+ #ifndef bit_MMX -+ #define bit_MMX (1 << 23) -+ #endif -+ #ifndef bit_SSE -+ #define bit_SSE (1 << 25) -+ #endif -+ #ifndef bit_SSE2 -+ #define bit_SSE2 (1 << 26) -+ #endif - #endif - - - ////////////////////////////////////////////////////////////////////////////// - // - // processor instructions extension detection routines - // - ////////////////////////////////////////////////////////////////////////////// diff --git a/media/libsoundtouch/src/AAFilter.cpp b/media/libsoundtouch/src/AAFilter.cpp deleted file mode 100644 index 29de4096b4b..00000000000 --- a/media/libsoundtouch/src/AAFilter.cpp +++ /dev/null @@ -1,184 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// FIR low-pass (anti-alias) filter with filter coefficient design routine and -/// MMX optimization. -/// -/// Anti-alias filter is used to prevent folding of high frequencies when -/// transposing the sample rate with interpolation. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2009-01-11 03:34:24 -0800 (Sun, 11 Jan 2009) $ -// File revision : $Revision: 4 $ -// -// $Id: AAFilter.cpp 45 2009-01-11 11:34:24Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include "AAFilter.h" -#include "FIRFilter.h" - -using namespace soundtouch; - -#define PI 3.141592655357989 -#define TWOPI (2 * PI) - -/***************************************************************************** - * - * Implementation of the class 'AAFilter' - * - *****************************************************************************/ - -AAFilter::AAFilter(uint len) -{ - pFIR = FIRFilter::newInstance(); - cutoffFreq = 0.5; - setLength(len); -} - - - -AAFilter::~AAFilter() -{ - delete pFIR; -} - - - -// Sets new anti-alias filter cut-off edge frequency, scaled to -// sampling frequency (nyquist frequency = 0.5). -// The filter will cut frequencies higher than the given frequency. -void AAFilter::setCutoffFreq(double newCutoffFreq) -{ - cutoffFreq = newCutoffFreq; - calculateCoeffs(); -} - - - -// Sets number of FIR filter taps -void AAFilter::setLength(uint newLength) -{ - length = newLength; - calculateCoeffs(); -} - - - -// Calculates coefficients for a low-pass FIR filter using Hamming window -void AAFilter::calculateCoeffs() -{ - uint i; - double cntTemp, temp, tempCoeff,h, w; - double fc2, wc; - double scaleCoeff, sum; - double *work; - SAMPLETYPE *coeffs; - - assert(length >= 2); - assert(length % 4 == 0); - assert(cutoffFreq >= 0); - assert(cutoffFreq <= 0.5); - - work = new double[length]; - coeffs = new SAMPLETYPE[length]; - - fc2 = 2.0 * cutoffFreq; - wc = PI * fc2; - tempCoeff = TWOPI / (double)length; - - sum = 0; - for (i = 0; i < length; i ++) - { - cntTemp = (double)i - (double)(length / 2); - - temp = cntTemp * wc; - if (temp != 0) - { - h = fc2 * sin(temp) / temp; // sinc function - } - else - { - h = 1.0; - } - w = 0.54 + 0.46 * cos(tempCoeff * cntTemp); // hamming window - - temp = w * h; - work[i] = temp; - - // calc net sum of coefficients - sum += temp; - } - - // ensure the sum of coefficients is larger than zero - assert(sum > 0); - - // ensure we've really designed a lowpass filter... - assert(work[length/2] > 0); - assert(work[length/2 + 1] > -1e-6); - assert(work[length/2 - 1] > -1e-6); - - // Calculate a scaling coefficient in such a way that the result can be - // divided by 16384 - scaleCoeff = 16384.0f / sum; - - for (i = 0; i < length; i ++) - { - // scale & round to nearest integer - temp = work[i] * scaleCoeff; - temp += (temp >= 0) ? 0.5 : -0.5; - // ensure no overfloods - assert(temp >= -32768 && temp <= 32767); - coeffs[i] = (SAMPLETYPE)temp; - } - - // Set coefficients. Use divide factor 14 => divide result by 2^14 = 16384 - pFIR->setCoefficients(coeffs, length, 14); - - delete[] work; - delete[] coeffs; -} - - -// Applies the filter to the given sequence of samples. -// Note : The amount of outputted samples is by value of 'filter length' -// smaller than the amount of input samples. -uint AAFilter::evaluate(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) const -{ - return pFIR->evaluate(dest, src, numSamples, numChannels); -} - - -uint AAFilter::getLength() const -{ - return pFIR->getLength(); -} diff --git a/media/libsoundtouch/src/AAFilter.h b/media/libsoundtouch/src/AAFilter.h deleted file mode 100644 index 7f172842e84..00000000000 --- a/media/libsoundtouch/src/AAFilter.h +++ /dev/null @@ -1,91 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo -/// while maintaining the original pitch by using a time domain WSOLA-like method -/// with several performance-increasing tweaks. -/// -/// Anti-alias filter is used to prevent folding of high frequencies when -/// transposing the sample rate with interpolation. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2008-02-10 08:26:55 -0800 (Sun, 10 Feb 2008) $ -// File revision : $Revision: 4 $ -// -// $Id: AAFilter.h 11 2008-02-10 16:26:55Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef AAFilter_H -#define AAFilter_H - -#include "STTypes.h" - -namespace soundtouch -{ - -class AAFilter -{ -protected: - class FIRFilter *pFIR; - - /// Low-pass filter cut-off frequency, negative = invalid - double cutoffFreq; - - /// num of filter taps - uint length; - - /// Calculate the FIR coefficients realizing the given cutoff-frequency - void calculateCoeffs(); -public: - AAFilter(uint length); - - ~AAFilter(); - - /// Sets new anti-alias filter cut-off edge frequency, scaled to sampling - /// frequency (nyquist frequency = 0.5). The filter will cut off the - /// frequencies than that. - void setCutoffFreq(double newCutoffFreq); - - /// Sets number of FIR filter taps, i.e. ~filter complexity - void setLength(uint newLength); - - uint getLength() const; - - /// Applies the filter to the given sequence of samples. - /// Note : The amount of outputted samples is by value of 'filter length' - /// smaller than the amount of input samples. - uint evaluate(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples, - uint numChannels) const; -}; - -} - -#endif diff --git a/media/libsoundtouch/src/FIFOSampleBuffer.cpp b/media/libsoundtouch/src/FIFOSampleBuffer.cpp deleted file mode 100644 index bda169276fb..00000000000 --- a/media/libsoundtouch/src/FIFOSampleBuffer.cpp +++ /dev/null @@ -1,274 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// A buffer class for temporarily storaging sound samples, operates as a -/// first-in-first-out pipe. -/// -/// Samples are added to the end of the sample buffer with the 'putSamples' -/// function, and are received from the beginning of the buffer by calling -/// the 'receiveSamples' function. The class automatically removes the -/// outputted samples from the buffer, as well as grows the buffer size -/// whenever necessary. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ -// File revision : $Revision: 4 $ -// -// $Id: FIFOSampleBuffer.cpp 143 2012-06-13 19:29:53Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -#include "FIFOSampleBuffer.h" - -using namespace soundtouch; - -// Constructor -FIFOSampleBuffer::FIFOSampleBuffer(int numChannels) -{ - assert(numChannels > 0); - sizeInBytes = 0; // reasonable initial value - buffer = NULL; - bufferUnaligned = NULL; - samplesInBuffer = 0; - bufferPos = 0; - channels = (uint)numChannels; - ensureCapacity(32); // allocate initial capacity -} - - -// destructor -FIFOSampleBuffer::~FIFOSampleBuffer() -{ - delete[] bufferUnaligned; - bufferUnaligned = NULL; - buffer = NULL; -} - - -// Sets number of channels, 1 = mono, 2 = stereo -void FIFOSampleBuffer::setChannels(int numChannels) -{ - uint usedBytes; - - assert(numChannels > 0); - usedBytes = channels * samplesInBuffer; - channels = (uint)numChannels; - samplesInBuffer = usedBytes / channels; -} - - -// if output location pointer 'bufferPos' isn't zero, 'rewinds' the buffer and -// zeroes this pointer by copying samples from the 'bufferPos' pointer -// location on to the beginning of the buffer. -void FIFOSampleBuffer::rewind() -{ - if (buffer && bufferPos) - { - memmove(buffer, ptrBegin(), sizeof(SAMPLETYPE) * channels * samplesInBuffer); - bufferPos = 0; - } -} - - -// Adds 'numSamples' pcs of samples from the 'samples' memory position to -// the sample buffer. -void FIFOSampleBuffer::putSamples(const SAMPLETYPE *samples, uint nSamples) -{ - memcpy(ptrEnd(nSamples), samples, sizeof(SAMPLETYPE) * nSamples * channels); - samplesInBuffer += nSamples; -} - - -// Increases the number of samples in the buffer without copying any actual -// samples. -// -// This function is used to update the number of samples in the sample buffer -// when accessing the buffer directly with 'ptrEnd' function. Please be -// careful though! -void FIFOSampleBuffer::putSamples(uint nSamples) -{ - uint req; - - req = samplesInBuffer + nSamples; - ensureCapacity(req); - samplesInBuffer += nSamples; -} - - -// Returns a pointer to the end of the used part of the sample buffer (i.e. -// where the new samples are to be inserted). This function may be used for -// inserting new samples into the sample buffer directly. Please be careful! -// -// Parameter 'slackCapacity' tells the function how much free capacity (in -// terms of samples) there _at least_ should be, in order to the caller to -// succesfully insert all the required samples to the buffer. When necessary, -// the function grows the buffer size to comply with this requirement. -// -// When using this function as means for inserting new samples, also remember -// to increase the sample count afterwards, by calling the -// 'putSamples(numSamples)' function. -SAMPLETYPE *FIFOSampleBuffer::ptrEnd(uint slackCapacity) -{ - ensureCapacity(samplesInBuffer + slackCapacity); - return buffer + samplesInBuffer * channels; -} - - -// Returns a pointer to the beginning of the currently non-outputted samples. -// This function is provided for accessing the output samples directly. -// Please be careful! -// -// When using this function to output samples, also remember to 'remove' the -// outputted samples from the buffer by calling the -// 'receiveSamples(numSamples)' function -SAMPLETYPE *FIFOSampleBuffer::ptrBegin() -{ - assert(buffer); - return buffer + bufferPos * channels; -} - - -// Ensures that the buffer has enought capacity, i.e. space for _at least_ -// 'capacityRequirement' number of samples. The buffer is grown in steps of -// 4 kilobytes to eliminate the need for frequently growing up the buffer, -// as well as to round the buffer size up to the virtual memory page size. -void FIFOSampleBuffer::ensureCapacity(uint capacityRequirement) -{ - SAMPLETYPE *tempUnaligned, *temp; - - if (capacityRequirement > getCapacity()) - { - // enlarge the buffer in 4kbyte steps (round up to next 4k boundary) - sizeInBytes = (capacityRequirement * channels * sizeof(SAMPLETYPE) + 4095) & (uint)-4096; - assert(sizeInBytes % 2 == 0); - tempUnaligned = new SAMPLETYPE[sizeInBytes / sizeof(SAMPLETYPE) + 16 / sizeof(SAMPLETYPE)]; - if (tempUnaligned == NULL) - { - ST_THROW_RT_ERROR("Couldn't allocate memory!\n"); - } - // Align the buffer to begin at 16byte cache line boundary for optimal performance - temp = (SAMPLETYPE *)(((ulong)tempUnaligned + 15) & (ulong)-16); - if (samplesInBuffer) - { - memcpy(temp, ptrBegin(), samplesInBuffer * channels * sizeof(SAMPLETYPE)); - } - delete[] bufferUnaligned; - buffer = temp; - bufferUnaligned = tempUnaligned; - bufferPos = 0; - } - else - { - // simply rewind the buffer (if necessary) - rewind(); - } -} - - -// Returns the current buffer capacity in terms of samples -uint FIFOSampleBuffer::getCapacity() const -{ - return sizeInBytes / (channels * sizeof(SAMPLETYPE)); -} - - -// Returns the number of samples currently in the buffer -uint FIFOSampleBuffer::numSamples() const -{ - return samplesInBuffer; -} - - -// Output samples from beginning of the sample buffer. Copies demanded number -// of samples to output and removes them from the sample buffer. If there -// are less than 'numsample' samples in the buffer, returns all available. -// -// Returns number of samples copied. -uint FIFOSampleBuffer::receiveSamples(SAMPLETYPE *output, uint maxSamples) -{ - uint num; - - num = (maxSamples > samplesInBuffer) ? samplesInBuffer : maxSamples; - - memcpy(output, ptrBegin(), channels * sizeof(SAMPLETYPE) * num); - return receiveSamples(num); -} - - -// Removes samples from the beginning of the sample buffer without copying them -// anywhere. Used to reduce the number of samples in the buffer, when accessing -// the sample buffer with the 'ptrBegin' function. -uint FIFOSampleBuffer::receiveSamples(uint maxSamples) -{ - if (maxSamples >= samplesInBuffer) - { - uint temp; - - temp = samplesInBuffer; - samplesInBuffer = 0; - return temp; - } - - samplesInBuffer -= maxSamples; - bufferPos += maxSamples; - - return maxSamples; -} - - -// Returns nonzero if the sample buffer is empty -int FIFOSampleBuffer::isEmpty() const -{ - return (samplesInBuffer == 0) ? 1 : 0; -} - - -// Clears the sample buffer -void FIFOSampleBuffer::clear() -{ - samplesInBuffer = 0; - bufferPos = 0; -} - - -/// allow trimming (downwards) amount of samples in pipeline. -/// Returns adjusted amount of samples -uint FIFOSampleBuffer::adjustAmountOfSamples(uint numSamples) -{ - if (numSamples < samplesInBuffer) - { - samplesInBuffer = numSamples; - } - return samplesInBuffer; -} - diff --git a/media/libsoundtouch/src/FIFOSampleBuffer.h b/media/libsoundtouch/src/FIFOSampleBuffer.h deleted file mode 100644 index 34702d94c76..00000000000 --- a/media/libsoundtouch/src/FIFOSampleBuffer.h +++ /dev/null @@ -1,178 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// A buffer class for temporarily storaging sound samples, operates as a -/// first-in-first-out pipe. -/// -/// Samples are added to the end of the sample buffer with the 'putSamples' -/// function, and are received from the beginning of the buffer by calling -/// the 'receiveSamples' function. The class automatically removes the -/// output samples from the buffer as well as grows the storage size -/// whenever necessary. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ -// File revision : $Revision: 4 $ -// -// $Id: FIFOSampleBuffer.h 143 2012-06-13 19:29:53Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef FIFOSampleBuffer_H -#define FIFOSampleBuffer_H - -#include "FIFOSamplePipe.h" - -namespace soundtouch -{ - -/// Sample buffer working in FIFO (first-in-first-out) principle. The class takes -/// care of storage size adjustment and data moving during input/output operations. -/// -/// Notice that in case of stereo audio, one sample is considered to consist of -/// both channel data. -class FIFOSampleBuffer : public FIFOSamplePipe -{ -private: - /// Sample buffer. - SAMPLETYPE *buffer; - - // Raw unaligned buffer memory. 'buffer' is made aligned by pointing it to first - // 16-byte aligned location of this buffer - SAMPLETYPE *bufferUnaligned; - - /// Sample buffer size in bytes - uint sizeInBytes; - - /// How many samples are currently in buffer. - uint samplesInBuffer; - - /// Channels, 1=mono, 2=stereo. - uint channels; - - /// Current position pointer to the buffer. This pointer is increased when samples are - /// removed from the pipe so that it's necessary to actually rewind buffer (move data) - /// only new data when is put to the pipe. - uint bufferPos; - - /// Rewind the buffer by moving data from position pointed by 'bufferPos' to real - /// beginning of the buffer. - void rewind(); - - /// Ensures that the buffer has capacity for at least this many samples. - void ensureCapacity(uint capacityRequirement); - - /// Returns current capacity. - uint getCapacity() const; - -public: - - /// Constructor - FIFOSampleBuffer(int numChannels = 2 ///< Number of channels, 1=mono, 2=stereo. - ///< Default is stereo. - ); - - /// destructor - ~FIFOSampleBuffer(); - - /// Returns a pointer to the beginning of the output samples. - /// This function is provided for accessing the output samples directly. - /// Please be careful for not to corrupt the book-keeping! - /// - /// When using this function to output samples, also remember to 'remove' the - /// output samples from the buffer by calling the - /// 'receiveSamples(numSamples)' function - virtual SAMPLETYPE *ptrBegin(); - - /// Returns a pointer to the end of the used part of the sample buffer (i.e. - /// where the new samples are to be inserted). This function may be used for - /// inserting new samples into the sample buffer directly. Please be careful - /// not corrupt the book-keeping! - /// - /// When using this function as means for inserting new samples, also remember - /// to increase the sample count afterwards, by calling the - /// 'putSamples(numSamples)' function. - SAMPLETYPE *ptrEnd( - uint slackCapacity ///< How much free capacity (in samples) there _at least_ - ///< should be so that the caller can succesfully insert the - ///< desired samples to the buffer. If necessary, the function - ///< grows the buffer size to comply with this requirement. - ); - - /// Adds 'numSamples' pcs of samples from the 'samples' memory position to - /// the sample buffer. - virtual void putSamples(const SAMPLETYPE *samples, ///< Pointer to samples. - uint numSamples ///< Number of samples to insert. - ); - - /// Adjusts the book-keeping to increase number of samples in the buffer without - /// copying any actual samples. - /// - /// This function is used to update the number of samples in the sample buffer - /// when accessing the buffer directly with 'ptrEnd' function. Please be - /// careful though! - virtual void putSamples(uint numSamples ///< Number of samples been inserted. - ); - - /// Output samples from beginning of the sample buffer. Copies requested samples to - /// output buffer and removes them from the sample buffer. If there are less than - /// 'numsample' samples in the buffer, returns all that available. - /// - /// \return Number of samples returned. - virtual uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples. - uint maxSamples ///< How many samples to receive at max. - ); - - /// Adjusts book-keeping so that given number of samples are removed from beginning of the - /// sample buffer without copying them anywhere. - /// - /// Used to reduce the number of samples in the buffer when accessing the sample buffer directly - /// with 'ptrBegin' function. - virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe. - ); - - /// Returns number of samples currently available. - virtual uint numSamples() const; - - /// Sets number of channels, 1 = mono, 2 = stereo. - void setChannels(int numChannels); - - /// Returns nonzero if there aren't any samples available for outputting. - virtual int isEmpty() const; - - /// Clears all the samples. - virtual void clear(); - - /// allow trimming (downwards) amount of samples in pipeline. - /// Returns adjusted amount of samples - uint adjustAmountOfSamples(uint numSamples); -}; - -} - -#endif diff --git a/media/libsoundtouch/src/FIFOSamplePipe.h b/media/libsoundtouch/src/FIFOSamplePipe.h deleted file mode 100644 index dde528c8664..00000000000 --- a/media/libsoundtouch/src/FIFOSamplePipe.h +++ /dev/null @@ -1,234 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// 'FIFOSamplePipe' : An abstract base class for classes that manipulate sound -/// samples by operating like a first-in-first-out pipe: New samples are fed -/// into one end of the pipe with the 'putSamples' function, and the processed -/// samples are received from the other end with the 'receiveSamples' function. -/// -/// 'FIFOProcessor' : A base class for classes the do signal processing with -/// the samples while operating like a first-in-first-out pipe. When samples -/// are input with the 'putSamples' function, the class processes them -/// and moves the processed samples to the given 'output' pipe object, which -/// may be either another processing stage, or a fifo sample buffer object. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ -// File revision : $Revision: 4 $ -// -// $Id: FIFOSamplePipe.h 143 2012-06-13 19:29:53Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef FIFOSamplePipe_H -#define FIFOSamplePipe_H - -#include -#include -#include "STTypes.h" - -namespace soundtouch -{ - -/// Abstract base class for FIFO (first-in-first-out) sample processing classes. -class FIFOSamplePipe -{ -public: - // virtual default destructor - virtual ~FIFOSamplePipe() {} - - - /// Returns a pointer to the beginning of the output samples. - /// This function is provided for accessing the output samples directly. - /// Please be careful for not to corrupt the book-keeping! - /// - /// When using this function to output samples, also remember to 'remove' the - /// output samples from the buffer by calling the - /// 'receiveSamples(numSamples)' function - virtual SAMPLETYPE *ptrBegin() = 0; - - /// Adds 'numSamples' pcs of samples from the 'samples' memory position to - /// the sample buffer. - virtual void putSamples(const SAMPLETYPE *samples, ///< Pointer to samples. - uint numSamples ///< Number of samples to insert. - ) = 0; - - - // Moves samples from the 'other' pipe instance to this instance. - void moveSamples(FIFOSamplePipe &other ///< Other pipe instance where from the receive the data. - ) - { - int oNumSamples = other.numSamples(); - - putSamples(other.ptrBegin(), oNumSamples); - other.receiveSamples(oNumSamples); - }; - - /// Output samples from beginning of the sample buffer. Copies requested samples to - /// output buffer and removes them from the sample buffer. If there are less than - /// 'numsample' samples in the buffer, returns all that available. - /// - /// \return Number of samples returned. - virtual uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples. - uint maxSamples ///< How many samples to receive at max. - ) = 0; - - /// Adjusts book-keeping so that given number of samples are removed from beginning of the - /// sample buffer without copying them anywhere. - /// - /// Used to reduce the number of samples in the buffer when accessing the sample buffer directly - /// with 'ptrBegin' function. - virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe. - ) = 0; - - /// Returns number of samples currently available. - virtual uint numSamples() const = 0; - - // Returns nonzero if there aren't any samples available for outputting. - virtual int isEmpty() const = 0; - - /// Clears all the samples. - virtual void clear() = 0; - - /// allow trimming (downwards) amount of samples in pipeline. - /// Returns adjusted amount of samples - virtual uint adjustAmountOfSamples(uint numSamples) = 0; - -}; - - - -/// Base-class for sound processing routines working in FIFO principle. With this base -/// class it's easy to implement sound processing stages that can be chained together, -/// so that samples that are fed into beginning of the pipe automatically go through -/// all the processing stages. -/// -/// When samples are input to this class, they're first processed and then put to -/// the FIFO pipe that's defined as output of this class. This output pipe can be -/// either other processing stage or a FIFO sample buffer. -class FIFOProcessor :public FIFOSamplePipe -{ -protected: - /// Internal pipe where processed samples are put. - FIFOSamplePipe *output; - - /// Sets output pipe. - void setOutPipe(FIFOSamplePipe *pOutput) - { - assert(output == NULL); - assert(pOutput != NULL); - output = pOutput; - } - - - /// Constructor. Doesn't define output pipe; it has to be set be - /// 'setOutPipe' function. - FIFOProcessor() - { - output = NULL; - } - - - /// Constructor. Configures output pipe. - FIFOProcessor(FIFOSamplePipe *pOutput ///< Output pipe. - ) - { - output = pOutput; - } - - - /// Destructor. - virtual ~FIFOProcessor() - { - } - - - /// Returns a pointer to the beginning of the output samples. - /// This function is provided for accessing the output samples directly. - /// Please be careful for not to corrupt the book-keeping! - /// - /// When using this function to output samples, also remember to 'remove' the - /// output samples from the buffer by calling the - /// 'receiveSamples(numSamples)' function - virtual SAMPLETYPE *ptrBegin() - { - return output->ptrBegin(); - } - -public: - - /// Output samples from beginning of the sample buffer. Copies requested samples to - /// output buffer and removes them from the sample buffer. If there are less than - /// 'numsample' samples in the buffer, returns all that available. - /// - /// \return Number of samples returned. - virtual uint receiveSamples(SAMPLETYPE *outBuffer, ///< Buffer where to copy output samples. - uint maxSamples ///< How many samples to receive at max. - ) - { - return output->receiveSamples(outBuffer, maxSamples); - } - - - /// Adjusts book-keeping so that given number of samples are removed from beginning of the - /// sample buffer without copying them anywhere. - /// - /// Used to reduce the number of samples in the buffer when accessing the sample buffer directly - /// with 'ptrBegin' function. - virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe. - ) - { - return output->receiveSamples(maxSamples); - } - - - /// Returns number of samples currently available. - virtual uint numSamples() const - { - return output->numSamples(); - } - - - /// Returns nonzero if there aren't any samples available for outputting. - virtual int isEmpty() const - { - return output->isEmpty(); - } - - /// allow trimming (downwards) amount of samples in pipeline. - /// Returns adjusted amount of samples - virtual uint adjustAmountOfSamples(uint numSamples) - { - return output->adjustAmountOfSamples(numSamples); - } - -}; - -} - -#endif diff --git a/media/libsoundtouch/src/FIRFilter.cpp b/media/libsoundtouch/src/FIRFilter.cpp deleted file mode 100644 index 3ba568f642f..00000000000 --- a/media/libsoundtouch/src/FIRFilter.cpp +++ /dev/null @@ -1,259 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// General FIR digital filter routines with MMX optimization. -/// -/// Note : MMX optimized functions reside in a separate, platform-specific file, -/// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2011-09-02 11:56:11 -0700 (Fri, 02 Sep 2011) $ -// File revision : $Revision: 4 $ -// -// $Id: FIRFilter.cpp 131 2011-09-02 18:56:11Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include "FIRFilter.h" -#include "cpu_detect.h" - -using namespace soundtouch; - -/***************************************************************************** - * - * Implementation of the class 'FIRFilter' - * - *****************************************************************************/ - -FIRFilter::FIRFilter() -{ - resultDivFactor = 0; - resultDivider = 0; - length = 0; - lengthDiv8 = 0; - filterCoeffs = NULL; -} - - -FIRFilter::~FIRFilter() -{ - delete[] filterCoeffs; -} - -// Usual C-version of the filter routine for stereo sound -uint FIRFilter::evaluateFilterStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples) const -{ - uint i, j, end; - LONG_SAMPLETYPE suml, sumr; -#ifdef SOUNDTOUCH_FLOAT_SAMPLES - // when using floating point samples, use a scaler instead of a divider - // because division is much slower operation than multiplying. - double dScaler = 1.0 / (double)resultDivider; -#endif - - assert(length != 0); - assert(src != NULL); - assert(dest != NULL); - assert(filterCoeffs != NULL); - - end = 2 * (numSamples - length); - - for (j = 0; j < end; j += 2) - { - const SAMPLETYPE *ptr; - - suml = sumr = 0; - ptr = src + j; - - for (i = 0; i < length; i += 4) - { - // loop is unrolled by factor of 4 here for efficiency - suml += ptr[2 * i + 0] * filterCoeffs[i + 0] + - ptr[2 * i + 2] * filterCoeffs[i + 1] + - ptr[2 * i + 4] * filterCoeffs[i + 2] + - ptr[2 * i + 6] * filterCoeffs[i + 3]; - sumr += ptr[2 * i + 1] * filterCoeffs[i + 0] + - ptr[2 * i + 3] * filterCoeffs[i + 1] + - ptr[2 * i + 5] * filterCoeffs[i + 2] + - ptr[2 * i + 7] * filterCoeffs[i + 3]; - } - -#ifdef SOUNDTOUCH_INTEGER_SAMPLES - suml >>= resultDivFactor; - sumr >>= resultDivFactor; - // saturate to 16 bit integer limits - suml = (suml < -32768) ? -32768 : (suml > 32767) ? 32767 : suml; - // saturate to 16 bit integer limits - sumr = (sumr < -32768) ? -32768 : (sumr > 32767) ? 32767 : sumr; -#else - suml *= dScaler; - sumr *= dScaler; -#endif // SOUNDTOUCH_INTEGER_SAMPLES - dest[j] = (SAMPLETYPE)suml; - dest[j + 1] = (SAMPLETYPE)sumr; - } - return numSamples - length; -} - - - - -// Usual C-version of the filter routine for mono sound -uint FIRFilter::evaluateFilterMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples) const -{ - uint i, j, end; - LONG_SAMPLETYPE sum; -#ifdef SOUNDTOUCH_FLOAT_SAMPLES - // when using floating point samples, use a scaler instead of a divider - // because division is much slower operation than multiplying. - double dScaler = 1.0 / (double)resultDivider; -#endif - - - assert(length != 0); - - end = numSamples - length; - for (j = 0; j < end; j ++) - { - sum = 0; - for (i = 0; i < length; i += 4) - { - // loop is unrolled by factor of 4 here for efficiency - sum += src[i + 0] * filterCoeffs[i + 0] + - src[i + 1] * filterCoeffs[i + 1] + - src[i + 2] * filterCoeffs[i + 2] + - src[i + 3] * filterCoeffs[i + 3]; - } -#ifdef SOUNDTOUCH_INTEGER_SAMPLES - sum >>= resultDivFactor; - // saturate to 16 bit integer limits - sum = (sum < -32768) ? -32768 : (sum > 32767) ? 32767 : sum; -#else - sum *= dScaler; -#endif // SOUNDTOUCH_INTEGER_SAMPLES - dest[j] = (SAMPLETYPE)sum; - src ++; - } - return end; -} - - -// Set filter coeffiecients and length. -// -// Throws an exception if filter length isn't divisible by 8 -void FIRFilter::setCoefficients(const SAMPLETYPE *coeffs, uint newLength, uint uResultDivFactor) -{ - assert(newLength > 0); - if (newLength % 8) ST_THROW_RT_ERROR("FIR filter length not divisible by 8"); - - lengthDiv8 = newLength / 8; - length = lengthDiv8 * 8; - assert(length == newLength); - - resultDivFactor = uResultDivFactor; - resultDivider = (SAMPLETYPE)::pow(2.0, (int)resultDivFactor); - - delete[] filterCoeffs; - filterCoeffs = new SAMPLETYPE[length]; - memcpy(filterCoeffs, coeffs, length * sizeof(SAMPLETYPE)); -} - - -uint FIRFilter::getLength() const -{ - return length; -} - - - -// Applies the filter to the given sequence of samples. -// -// Note : The amount of outputted samples is by value of 'filter_length' -// smaller than the amount of input samples. -uint FIRFilter::evaluate(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) const -{ - assert(numChannels == 1 || numChannels == 2); - - assert(length > 0); - assert(lengthDiv8 * 8 == length); - if (numSamples < length) return 0; - if (numChannels == 2) - { - return evaluateFilterStereo(dest, src, numSamples); - } else { - return evaluateFilterMono(dest, src, numSamples); - } -} - - - -// Operator 'new' is overloaded so that it automatically creates a suitable instance -// depending on if we've a MMX-capable CPU available or not. -void * FIRFilter::operator new(size_t s) -{ - // Notice! don't use "new FIRFilter" directly, use "newInstance" to create a new instance instead! - ST_THROW_RT_ERROR("Error in FIRFilter::new: Don't use 'new FIRFilter', use 'newInstance' member instead!"); - return newInstance(); -} - - -FIRFilter * FIRFilter::newInstance() -{ - uint uExtensions; - - uExtensions = detectCPUextensions(); - - // Check if MMX/SSE instruction set extensions supported by CPU - -#ifdef SOUNDTOUCH_ALLOW_MMX - // MMX routines available only with integer sample types - if (uExtensions & SUPPORT_MMX) - { - return ::new FIRFilterMMX; - } - else -#endif // SOUNDTOUCH_ALLOW_MMX - -#ifdef SOUNDTOUCH_ALLOW_SSE - if (uExtensions & SUPPORT_SSE) - { - // SSE support - return ::new FIRFilterSSE; - } - else -#endif // SOUNDTOUCH_ALLOW_SSE - - { - // ISA optimizations not supported, use plain C version - return ::new FIRFilter; - } -} diff --git a/media/libsoundtouch/src/FIRFilter.h b/media/libsoundtouch/src/FIRFilter.h deleted file mode 100644 index 25ede16a8aa..00000000000 --- a/media/libsoundtouch/src/FIRFilter.h +++ /dev/null @@ -1,145 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// General FIR digital filter routines with MMX optimization. -/// -/// Note : MMX optimized functions reside in a separate, platform-specific file, -/// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2011-02-13 11:13:57 -0800 (Sun, 13 Feb 2011) $ -// File revision : $Revision: 4 $ -// -// $Id: FIRFilter.h 104 2011-02-13 19:13:57Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef FIRFilter_H -#define FIRFilter_H - -#include -#include "STTypes.h" - -namespace soundtouch -{ - -class FIRFilter -{ -protected: - // Number of FIR filter taps - uint length; - // Number of FIR filter taps divided by 8 - uint lengthDiv8; - - // Result divider factor in 2^k format - uint resultDivFactor; - - // Result divider value. - SAMPLETYPE resultDivider; - - // Memory for filter coefficients - SAMPLETYPE *filterCoeffs; - - virtual uint evaluateFilterStereo(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples) const; - virtual uint evaluateFilterMono(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples) const; - -public: - FIRFilter(); - virtual ~FIRFilter(); - - /// Operator 'new' is overloaded so that it automatically creates a suitable instance - /// depending on if we've a MMX-capable CPU available or not. - static void * operator new(size_t s); - - static FIRFilter *newInstance(); - - /// Applies the filter to the given sequence of samples. - /// Note : The amount of outputted samples is by value of 'filter_length' - /// smaller than the amount of input samples. - /// - /// \return Number of samples copied to 'dest'. - uint evaluate(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples, - uint numChannels) const; - - uint getLength() const; - - virtual void setCoefficients(const SAMPLETYPE *coeffs, - uint newLength, - uint uResultDivFactor); -}; - - -// Optional subclasses that implement CPU-specific optimizations: - -#ifdef SOUNDTOUCH_ALLOW_MMX - -/// Class that implements MMX optimized functions exclusive for 16bit integer samples type. - class FIRFilterMMX : public FIRFilter - { - protected: - short *filterCoeffsUnalign; - short *filterCoeffsAlign; - - virtual uint evaluateFilterStereo(short *dest, const short *src, uint numSamples) const; - public: - FIRFilterMMX(); - ~FIRFilterMMX(); - - virtual void setCoefficients(const short *coeffs, uint newLength, uint uResultDivFactor); - }; - -#endif // SOUNDTOUCH_ALLOW_MMX - - -#ifdef SOUNDTOUCH_ALLOW_SSE - /// Class that implements SSE optimized functions exclusive for floating point samples type. - class FIRFilterSSE : public FIRFilter - { - protected: - float *filterCoeffsUnalign; - float *filterCoeffsAlign; - - virtual uint evaluateFilterStereo(float *dest, const float *src, uint numSamples) const; - public: - FIRFilterSSE(); - ~FIRFilterSSE(); - - virtual void setCoefficients(const float *coeffs, uint newLength, uint uResultDivFactor); - }; - -#endif // SOUNDTOUCH_ALLOW_SSE - -} - -#endif // FIRFilter_H diff --git a/media/libsoundtouch/src/Makefile.in b/media/libsoundtouch/src/Makefile.in deleted file mode 100644 index 965d04318f8..00000000000 --- a/media/libsoundtouch/src/Makefile.in +++ /dev/null @@ -1,69 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -DEPTH = @DEPTH@ -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -MODULE = soundtouch -LIBRARY_NAME = soundtouch -SHORT_LIBNAME = soundt -FORCE_SHARED_LIB = 1 -VISIBILITY_FLAGS = -EXPORTS_NAMESPACES = soundtouch - -ifeq ($(OS_ARCH),WINNT) -ifndef GNU_CC -RCFILE = $(srcdir)/soundtouch.rc -RESFILE = $(srcdir)/soundtouch.res -endif -endif - - -EXTRA_DSO_LDOPTS += $(MOZALLOC_LIB) - -# Use abort() instead of exception in SoundTouch. -DEFINES += -DST_NO_EXCEPTION_HANDLING=1 - -EXPORTS_soundtouch = SoundTouch.h \ - STTypes.h \ - FIFOSamplePipe.h \ - soundtouch_config.h \ - $(NULL) - -CPPSRCS = AAFilter.cpp \ - cpu_detect_x86.cpp \ - FIFOSampleBuffer.cpp \ - FIRFilter.cpp \ - RateTransposer.cpp \ - SoundTouch.cpp \ - TDStretch.cpp \ - $(NULL) - -ifneq (,$(INTEL_ARCHITECTURE)) -ifdef MOZ_SAMPLE_TYPE_FLOAT32 -CPPSRCS += sse_optimized.cpp -else -CPPSRCS += mmx_optimized.cpp -endif -endif - -SOUNDTOUCH_LIBS = $(DLL_PREFIX)soundtouch$(DLL_SUFFIX) - -include $(topsrcdir)/config/rules.mk - -ifneq (,$(INTEL_ARCHITECTURE)) -ifdef GNU_CC -mmx_optimized.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 -sse_optimized.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 -endif -ifdef SOLARIS_SUNPRO_CXX -mmx_optimized.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 -sse_optimized.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 -endif -endif - diff --git a/media/libsoundtouch/src/RateTransposer.cpp b/media/libsoundtouch/src/RateTransposer.cpp deleted file mode 100644 index 6fb8a1eaeac..00000000000 --- a/media/libsoundtouch/src/RateTransposer.cpp +++ /dev/null @@ -1,626 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// Sample rate transposer. Changes sample rate by using linear interpolation -/// together with anti-alias filtering (first order interpolation with anti- -/// alias filtering should be quite adequate for this application) -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2011-09-02 11:56:11 -0700 (Fri, 02 Sep 2011) $ -// File revision : $Revision: 4 $ -// -// $Id: RateTransposer.cpp 131 2011-09-02 18:56:11Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include "RateTransposer.h" -#include "AAFilter.h" - -using namespace soundtouch; - - -/// A linear samplerate transposer class that uses integer arithmetics. -/// for the transposing. -class RateTransposerInteger : public RateTransposer -{ -protected: - int iSlopeCount; - int iRate; - SAMPLETYPE sPrevSampleL, sPrevSampleR; - - virtual void resetRegisters(); - - virtual uint transposeStereo(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples); - virtual uint transposeMono(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples); - -public: - RateTransposerInteger(); - virtual ~RateTransposerInteger(); - - /// Sets new target rate. Normal rate = 1.0, smaller values represent slower - /// rate, larger faster rates. - virtual void setRate(float newRate); - -}; - - -/// A linear samplerate transposer class that uses floating point arithmetics -/// for the transposing. -class RateTransposerFloat : public RateTransposer -{ -protected: - float fSlopeCount; - SAMPLETYPE sPrevSampleL, sPrevSampleR; - - virtual void resetRegisters(); - - virtual uint transposeStereo(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples); - virtual uint transposeMono(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples); - -public: - RateTransposerFloat(); - virtual ~RateTransposerFloat(); -}; - - - - -// Operator 'new' is overloaded so that it automatically creates a suitable instance -// depending on if we've a MMX/SSE/etc-capable CPU available or not. -void * RateTransposer::operator new(size_t s) -{ - ST_THROW_RT_ERROR("Error in RateTransoser::new: don't use \"new TDStretch\" directly, use \"newInstance\" to create a new instance instead!"); - return newInstance(); -} - - -RateTransposer *RateTransposer::newInstance() -{ -#ifdef SOUNDTOUCH_INTEGER_SAMPLES - return ::new RateTransposerInteger; -#else - return ::new RateTransposerFloat; -#endif -} - - -// Constructor -RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer) -{ - numChannels = 2; - bUseAAFilter = true; - fRate = 0; - - // Instantiates the anti-alias filter with default tap length - // of 32 - pAAFilter = new AAFilter(32); -} - - - -RateTransposer::~RateTransposer() -{ - delete pAAFilter; -} - - - -/// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable -void RateTransposer::enableAAFilter(bool newMode) -{ - bUseAAFilter = newMode; -} - - -/// Returns nonzero if anti-alias filter is enabled. -bool RateTransposer::isAAFilterEnabled() const -{ - return bUseAAFilter; -} - - -AAFilter *RateTransposer::getAAFilter() -{ - return pAAFilter; -} - - - -// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower -// iRate, larger faster iRates. -void RateTransposer::setRate(float newRate) -{ - double fCutoff; - - fRate = newRate; - - // design a new anti-alias filter - if (newRate > 1.0f) - { - fCutoff = 0.5f / newRate; - } - else - { - fCutoff = 0.5f * newRate; - } - pAAFilter->setCutoffFreq(fCutoff); -} - - -// Outputs as many samples of the 'outputBuffer' as possible, and if there's -// any room left, outputs also as many of the incoming samples as possible. -// The goal is to drive the outputBuffer empty. -// -// It's allowed for 'output' and 'input' parameters to point to the same -// memory position. -/* -void RateTransposer::flushStoreBuffer() -{ - if (storeBuffer.isEmpty()) return; - - outputBuffer.moveSamples(storeBuffer); -} -*/ - - -// Adds 'nSamples' pcs of samples from the 'samples' memory position into -// the input of the object. -void RateTransposer::putSamples(const SAMPLETYPE *samples, uint nSamples) -{ - processSamples(samples, nSamples); -} - - - -// Transposes up the sample rate, causing the observed playback 'rate' of the -// sound to decrease -void RateTransposer::upsample(const SAMPLETYPE *src, uint nSamples) -{ - uint count, sizeTemp, num; - - // If the parameter 'uRate' value is smaller than 'SCALE', first transpose - // the samples and then apply the anti-alias filter to remove aliasing. - - // First check that there's enough room in 'storeBuffer' - // (+16 is to reserve some slack in the destination buffer) - sizeTemp = (uint)((float)nSamples / fRate + 16.0f); - - // Transpose the samples, store the result into the end of "storeBuffer" - count = transpose(storeBuffer.ptrEnd(sizeTemp), src, nSamples); - storeBuffer.putSamples(count); - - // Apply the anti-alias filter to samples in "store output", output the - // result to "dest" - num = storeBuffer.numSamples(); - count = pAAFilter->evaluate(outputBuffer.ptrEnd(num), - storeBuffer.ptrBegin(), num, (uint)numChannels); - outputBuffer.putSamples(count); - - // Remove the processed samples from "storeBuffer" - storeBuffer.receiveSamples(count); -} - - -// Transposes down the sample rate, causing the observed playback 'rate' of the -// sound to increase -void RateTransposer::downsample(const SAMPLETYPE *src, uint nSamples) -{ - uint count, sizeTemp; - - // If the parameter 'uRate' value is larger than 'SCALE', first apply the - // anti-alias filter to remove high frequencies (prevent them from folding - // over the lover frequencies), then transpose. - - // Add the new samples to the end of the storeBuffer - storeBuffer.putSamples(src, nSamples); - - // Anti-alias filter the samples to prevent folding and output the filtered - // data to tempBuffer. Note : because of the FIR filter length, the - // filtering routine takes in 'filter_length' more samples than it outputs. - assert(tempBuffer.isEmpty()); - sizeTemp = storeBuffer.numSamples(); - - count = pAAFilter->evaluate(tempBuffer.ptrEnd(sizeTemp), - storeBuffer.ptrBegin(), sizeTemp, (uint)numChannels); - - if (count == 0) return; - - // Remove the filtered samples from 'storeBuffer' - storeBuffer.receiveSamples(count); - - // Transpose the samples (+16 is to reserve some slack in the destination buffer) - sizeTemp = (uint)((float)nSamples / fRate + 16.0f); - count = transpose(outputBuffer.ptrEnd(sizeTemp), tempBuffer.ptrBegin(), count); - outputBuffer.putSamples(count); -} - - -// Transposes sample rate by applying anti-alias filter to prevent folding. -// Returns amount of samples returned in the "dest" buffer. -// The maximum amount of samples that can be returned at a time is set by -// the 'set_returnBuffer_size' function. -void RateTransposer::processSamples(const SAMPLETYPE *src, uint nSamples) -{ - uint count; - uint sizeReq; - - if (nSamples == 0) return; - assert(pAAFilter); - - // If anti-alias filter is turned off, simply transpose without applying - // the filter - if (bUseAAFilter == false) - { - sizeReq = (uint)((float)nSamples / fRate + 1.0f); - count = transpose(outputBuffer.ptrEnd(sizeReq), src, nSamples); - outputBuffer.putSamples(count); - return; - } - - // Transpose with anti-alias filter - if (fRate < 1.0f) - { - upsample(src, nSamples); - } - else - { - downsample(src, nSamples); - } -} - - -// Transposes the sample rate of the given samples using linear interpolation. -// Returns the number of samples returned in the "dest" buffer -inline uint RateTransposer::transpose(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) -{ - if (numChannels == 2) - { - return transposeStereo(dest, src, nSamples); - } - else - { - return transposeMono(dest, src, nSamples); - } -} - - -// Sets the number of channels, 1 = mono, 2 = stereo -void RateTransposer::setChannels(int nChannels) -{ - assert(nChannels > 0); - if (numChannels == nChannels) return; - - assert(nChannels == 1 || nChannels == 2); - numChannels = nChannels; - - storeBuffer.setChannels(numChannels); - tempBuffer.setChannels(numChannels); - outputBuffer.setChannels(numChannels); - - // Inits the linear interpolation registers - resetRegisters(); -} - - -// Clears all the samples in the object -void RateTransposer::clear() -{ - outputBuffer.clear(); - storeBuffer.clear(); -} - - -// Returns nonzero if there aren't any samples available for outputting. -int RateTransposer::isEmpty() const -{ - int res; - - res = FIFOProcessor::isEmpty(); - if (res == 0) return 0; - return storeBuffer.isEmpty(); -} - - -////////////////////////////////////////////////////////////////////////////// -// -// RateTransposerInteger - integer arithmetic implementation -// - -/// fixed-point interpolation routine precision -#define SCALE 65536 - -// Constructor -RateTransposerInteger::RateTransposerInteger() : RateTransposer() -{ - // Notice: use local function calling syntax for sake of clarity, - // to indicate the fact that C++ constructor can't call virtual functions. - RateTransposerInteger::resetRegisters(); - RateTransposerInteger::setRate(1.0f); -} - - -RateTransposerInteger::~RateTransposerInteger() -{ -} - - -void RateTransposerInteger::resetRegisters() -{ - iSlopeCount = 0; - sPrevSampleL = - sPrevSampleR = 0; -} - - - -// Transposes the sample rate of the given samples using linear interpolation. -// 'Mono' version of the routine. Returns the number of samples returned in -// the "dest" buffer -uint RateTransposerInteger::transposeMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) -{ - unsigned int i, used; - LONG_SAMPLETYPE temp, vol1; - - if (nSamples == 0) return 0; // no samples, no work - - used = 0; - i = 0; - - // Process the last sample saved from the previous call first... - while (iSlopeCount <= SCALE) - { - vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); - temp = vol1 * sPrevSampleL + iSlopeCount * src[0]; - dest[i] = (SAMPLETYPE)(temp / SCALE); - i++; - iSlopeCount += iRate; - } - // now always (iSlopeCount > SCALE) - iSlopeCount -= SCALE; - - while (1) - { - while (iSlopeCount > SCALE) - { - iSlopeCount -= SCALE; - used ++; - if (used >= nSamples - 1) goto end; - } - vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); - temp = src[used] * vol1 + iSlopeCount * src[used + 1]; - dest[i] = (SAMPLETYPE)(temp / SCALE); - - i++; - iSlopeCount += iRate; - } -end: - // Store the last sample for the next round - sPrevSampleL = src[nSamples - 1]; - - return i; -} - - -// Transposes the sample rate of the given samples using linear interpolation. -// 'Stereo' version of the routine. Returns the number of samples returned in -// the "dest" buffer -uint RateTransposerInteger::transposeStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) -{ - unsigned int srcPos, i, used; - LONG_SAMPLETYPE temp, vol1; - - if (nSamples == 0) return 0; // no samples, no work - - used = 0; - i = 0; - - // Process the last sample saved from the sPrevSampleLious call first... - while (iSlopeCount <= SCALE) - { - vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); - temp = vol1 * sPrevSampleL + iSlopeCount * src[0]; - dest[2 * i] = (SAMPLETYPE)(temp / SCALE); - temp = vol1 * sPrevSampleR + iSlopeCount * src[1]; - dest[2 * i + 1] = (SAMPLETYPE)(temp / SCALE); - i++; - iSlopeCount += iRate; - } - // now always (iSlopeCount > SCALE) - iSlopeCount -= SCALE; - - while (1) - { - while (iSlopeCount > SCALE) - { - iSlopeCount -= SCALE; - used ++; - if (used >= nSamples - 1) goto end; - } - srcPos = 2 * used; - vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); - temp = src[srcPos] * vol1 + iSlopeCount * src[srcPos + 2]; - dest[2 * i] = (SAMPLETYPE)(temp / SCALE); - temp = src[srcPos + 1] * vol1 + iSlopeCount * src[srcPos + 3]; - dest[2 * i + 1] = (SAMPLETYPE)(temp / SCALE); - - i++; - iSlopeCount += iRate; - } -end: - // Store the last sample for the next round - sPrevSampleL = src[2 * nSamples - 2]; - sPrevSampleR = src[2 * nSamples - 1]; - - return i; -} - - -// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower -// iRate, larger faster iRates. -void RateTransposerInteger::setRate(float newRate) -{ - iRate = (int)(newRate * SCALE + 0.5f); - RateTransposer::setRate(newRate); -} - - -////////////////////////////////////////////////////////////////////////////// -// -// RateTransposerFloat - floating point arithmetic implementation -// -////////////////////////////////////////////////////////////////////////////// - -// Constructor -RateTransposerFloat::RateTransposerFloat() : RateTransposer() -{ - // Notice: use local function calling syntax for sake of clarity, - // to indicate the fact that C++ constructor can't call virtual functions. - RateTransposerFloat::resetRegisters(); - RateTransposerFloat::setRate(1.0f); -} - - -RateTransposerFloat::~RateTransposerFloat() -{ -} - - -void RateTransposerFloat::resetRegisters() -{ - fSlopeCount = 0; - sPrevSampleL = - sPrevSampleR = 0; -} - - - -// Transposes the sample rate of the given samples using linear interpolation. -// 'Mono' version of the routine. Returns the number of samples returned in -// the "dest" buffer -uint RateTransposerFloat::transposeMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) -{ - unsigned int i, used; - - used = 0; - i = 0; - - // Process the last sample saved from the previous call first... - while (fSlopeCount <= 1.0f) - { - dest[i] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleL + fSlopeCount * src[0]); - i++; - fSlopeCount += fRate; - } - fSlopeCount -= 1.0f; - - if (nSamples > 1) - { - while (1) - { - while (fSlopeCount > 1.0f) - { - fSlopeCount -= 1.0f; - used ++; - if (used >= nSamples - 1) goto end; - } - dest[i] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[used] + fSlopeCount * src[used + 1]); - i++; - fSlopeCount += fRate; - } - } -end: - // Store the last sample for the next round - sPrevSampleL = src[nSamples - 1]; - - return i; -} - - -// Transposes the sample rate of the given samples using linear interpolation. -// 'Mono' version of the routine. Returns the number of samples returned in -// the "dest" buffer -uint RateTransposerFloat::transposeStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) -{ - unsigned int srcPos, i, used; - - if (nSamples == 0) return 0; // no samples, no work - - used = 0; - i = 0; - - // Process the last sample saved from the sPrevSampleLious call first... - while (fSlopeCount <= 1.0f) - { - dest[2 * i] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleL + fSlopeCount * src[0]); - dest[2 * i + 1] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleR + fSlopeCount * src[1]); - i++; - fSlopeCount += fRate; - } - // now always (iSlopeCount > 1.0f) - fSlopeCount -= 1.0f; - - if (nSamples > 1) - { - while (1) - { - while (fSlopeCount > 1.0f) - { - fSlopeCount -= 1.0f; - used ++; - if (used >= nSamples - 1) goto end; - } - srcPos = 2 * used; - - dest[2 * i] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[srcPos] - + fSlopeCount * src[srcPos + 2]); - dest[2 * i + 1] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[srcPos + 1] - + fSlopeCount * src[srcPos + 3]); - - i++; - fSlopeCount += fRate; - } - } -end: - // Store the last sample for the next round - sPrevSampleL = src[2 * nSamples - 2]; - sPrevSampleR = src[2 * nSamples - 1]; - - return i; -} diff --git a/media/libsoundtouch/src/RateTransposer.h b/media/libsoundtouch/src/RateTransposer.h deleted file mode 100644 index c2256210d92..00000000000 --- a/media/libsoundtouch/src/RateTransposer.h +++ /dev/null @@ -1,159 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// Sample rate transposer. Changes sample rate by using linear interpolation -/// together with anti-alias filtering (first order interpolation with anti- -/// alias filtering should be quite adequate for this application). -/// -/// Use either of the derived classes of 'RateTransposerInteger' or -/// 'RateTransposerFloat' for corresponding integer/floating point tranposing -/// algorithm implementation. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2009-02-21 08:00:14 -0800 (Sat, 21 Feb 2009) $ -// File revision : $Revision: 4 $ -// -// $Id: RateTransposer.h 63 2009-02-21 16:00:14Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef RateTransposer_H -#define RateTransposer_H - -#include -#include "AAFilter.h" -#include "FIFOSamplePipe.h" -#include "FIFOSampleBuffer.h" - -#include "STTypes.h" - -namespace soundtouch -{ - -/// A common linear samplerate transposer class. -/// -/// Note: Use function "RateTransposer::newInstance()" to create a new class -/// instance instead of the "new" operator; that function automatically -/// chooses a correct implementation depending on if integer or floating -/// arithmetics are to be used. -class RateTransposer : public FIFOProcessor -{ -protected: - /// Anti-alias filter object - AAFilter *pAAFilter; - - float fRate; - - int numChannels; - - /// Buffer for collecting samples to feed the anti-alias filter between - /// two batches - FIFOSampleBuffer storeBuffer; - - /// Buffer for keeping samples between transposing & anti-alias filter - FIFOSampleBuffer tempBuffer; - - /// Output sample buffer - FIFOSampleBuffer outputBuffer; - - bool bUseAAFilter; - - virtual void resetRegisters() = 0; - - virtual uint transposeStereo(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples) = 0; - virtual uint transposeMono(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples) = 0; - inline uint transpose(SAMPLETYPE *dest, - const SAMPLETYPE *src, - uint numSamples); - - void downsample(const SAMPLETYPE *src, - uint numSamples); - void upsample(const SAMPLETYPE *src, - uint numSamples); - - /// Transposes sample rate by applying anti-alias filter to prevent folding. - /// Returns amount of samples returned in the "dest" buffer. - /// The maximum amount of samples that can be returned at a time is set by - /// the 'set_returnBuffer_size' function. - void processSamples(const SAMPLETYPE *src, - uint numSamples); - - -public: - RateTransposer(); - virtual ~RateTransposer(); - - /// Operator 'new' is overloaded so that it automatically creates a suitable instance - /// depending on if we're to use integer or floating point arithmetics. - static void *operator new(size_t s); - - /// Use this function instead of "new" operator to create a new instance of this class. - /// This function automatically chooses a correct implementation, depending on if - /// integer ot floating point arithmetics are to be used. - static RateTransposer *newInstance(); - - /// Returns the output buffer object - FIFOSamplePipe *getOutput() { return &outputBuffer; }; - - /// Returns the store buffer object - FIFOSamplePipe *getStore() { return &storeBuffer; }; - - /// Return anti-alias filter object - AAFilter *getAAFilter(); - - /// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable - void enableAAFilter(bool newMode); - - /// Returns nonzero if anti-alias filter is enabled. - bool isAAFilterEnabled() const; - - /// Sets new target rate. Normal rate = 1.0, smaller values represent slower - /// rate, larger faster rates. - virtual void setRate(float newRate); - - /// Sets the number of channels, 1 = mono, 2 = stereo - void setChannels(int channels); - - /// Adds 'numSamples' pcs of samples from the 'samples' memory position into - /// the input of the object. - void putSamples(const SAMPLETYPE *samples, uint numSamples); - - /// Clears all the samples in the object - void clear(); - - /// Returns nonzero if there aren't any samples available for outputting. - int isEmpty() const; -}; - -} - -#endif diff --git a/media/libsoundtouch/src/STTypes.h b/media/libsoundtouch/src/STTypes.h deleted file mode 100644 index bfd08f2da0e..00000000000 --- a/media/libsoundtouch/src/STTypes.h +++ /dev/null @@ -1,159 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// Common type definitions for SoundTouch audio processing library. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-04-01 10:01:42 -0700 (Sun, 01 Apr 2012) $ -// File revision : $Revision: 3 $ -// -// $Id: STTypes.h 136 2012-04-01 17:01:42Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef STTypes_H -#define STTypes_H - -typedef unsigned int uint; -typedef unsigned long ulong; - -#include "soundtouch_config.h" - -#ifdef WIN32 -#define EXPORT __declspec(dllexport) -#else -#define EXPORT -#endif - -namespace soundtouch -{ - /// Activate these undef's to overrule the possible sampletype - /// setting inherited from some other header file: - //#undef SOUNDTOUCH_INTEGER_SAMPLES - //#undef SOUNDTOUCH_FLOAT_SAMPLES - - #if !(SOUNDTOUCH_INTEGER_SAMPLES || SOUNDTOUCH_FLOAT_SAMPLES) - - /// Choose either 32bit floating point or 16bit integer sampletype - /// by choosing one of the following defines, unless this selection - /// has already been done in some other file. - //// - /// Notes: - /// - In Windows environment, choose the sample format with the - /// following defines. - /// - In GNU environment, the floating point samples are used by - /// default, but integer samples can be chosen by giving the - /// following switch to the configure script: - /// ./configure --enable-integer-samples - /// However, if you still prefer to select the sample format here - /// also in GNU environment, then please #undef the INTEGER_SAMPLE - /// and FLOAT_SAMPLE defines first as in comments above. - //#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples - #define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples - - #endif - - #if (_M_IX86 || __i386__ || __x86_64__ || _M_X64) - /// Define this to allow X86-specific assembler/intrinsic optimizations. - /// Notice that library contains also usual C++ versions of each of these - /// these routines, so if you're having difficulties getting the optimized - /// routines compiled for whatever reason, you may disable these optimizations - /// to make the library compile. - - #define SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS 1 - - /// In GNU environment, allow the user to override this setting by - /// giving the following switch to the configure script: - /// ./configure --disable-x86-optimizations - /// ./configure --enable-x86-optimizations=no - #ifdef SOUNDTOUCH_DISABLE_X86_OPTIMIZATIONS - #undef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS - #endif - #else - /// Always disable optimizations when not using a x86 systems. - #undef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS - - #endif - - // If defined, allows the SIMD-optimized routines to take minor shortcuts - // for improved performance. Undefine to require faithfully similar SIMD - // calculations as in normal C implementation. - #define SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION 1 - - - #ifdef SOUNDTOUCH_INTEGER_SAMPLES - // 16bit integer sample type - typedef short SAMPLETYPE; - // data type for sample accumulation: Use 32bit integer to prevent overflows - typedef long LONG_SAMPLETYPE; - - #ifdef SOUNDTOUCH_FLOAT_SAMPLES - // check that only one sample type is defined - #error "conflicting sample types defined" - #endif // SOUNDTOUCH_FLOAT_SAMPLES - - #ifdef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS - // Allow MMX optimizations - #define SOUNDTOUCH_ALLOW_MMX 1 - #endif - - #else - - // floating point samples - typedef float SAMPLETYPE; - // data type for sample accumulation: Use double to utilize full precision. - typedef double LONG_SAMPLETYPE; - - #ifdef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS - // Allow SSE optimizations - #define SOUNDTOUCH_ALLOW_SSE 1 - #endif - - #endif // SOUNDTOUCH_INTEGER_SAMPLES - -} - -// define ST_NO_EXCEPTION_HANDLING switch to disable throwing std exceptions: -#define ST_NO_EXCEPTION_HANDLING 1 -#ifdef ST_NO_EXCEPTION_HANDLING - // Exceptions disabled. Throw asserts instead if enabled. - #include - #define ST_THROW_RT_ERROR(x) {assert((const char *)x);} -#else - // use c++ standard exceptions - #include - #define ST_THROW_RT_ERROR(x) {throw std::runtime_error(x);} -#endif - -// When this #define is active, eliminates a clicking sound when the "rate" or "pitch" -// parameter setting crosses from value <1 to >=1 or vice versa during processing. -// Default is off as such crossover is untypical case and involves a slight sound -// quality compromise. -//#define SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER 1 - -#endif diff --git a/media/libsoundtouch/src/SoundTouch.cpp b/media/libsoundtouch/src/SoundTouch.cpp deleted file mode 100644 index 46de76cb2d4..00000000000 --- a/media/libsoundtouch/src/SoundTouch.cpp +++ /dev/null @@ -1,501 +0,0 @@ -////////////////////////////////////////////////////////////////////////////// -/// -/// SoundTouch - main class for tempo/pitch/rate adjusting routines. -/// -/// Notes: -/// - Initialize the SoundTouch object instance by setting up the sound stream -/// parameters with functions 'setSampleRate' and 'setChannels', then set -/// desired tempo/pitch/rate settings with the corresponding functions. -/// -/// - The SoundTouch class behaves like a first-in-first-out pipeline: The -/// samples that are to be processed are fed into one of the pipe by calling -/// function 'putSamples', while the ready processed samples can be read -/// from the other end of the pipeline with function 'receiveSamples'. -/// -/// - The SoundTouch processing classes require certain sized 'batches' of -/// samples in order to process the sound. For this reason the classes buffer -/// incoming samples until there are enough of samples available for -/// processing, then they carry out the processing step and consequently -/// make the processed samples available for outputting. -/// -/// - For the above reason, the processing routines introduce a certain -/// 'latency' between the input and output, so that the samples input to -/// SoundTouch may not be immediately available in the output, and neither -/// the amount of outputtable samples may not immediately be in direct -/// relationship with the amount of previously input samples. -/// -/// - The tempo/pitch/rate control parameters can be altered during processing. -/// Please notice though that they aren't currently protected by semaphores, -/// so in multi-thread application external semaphore protection may be -/// required. -/// -/// - This class utilizes classes 'TDStretch' for tempo change (without modifying -/// pitch) and 'RateTransposer' for changing the playback rate (that is, both -/// tempo and pitch in the same ratio) of the sound. The third available control -/// 'pitch' (change pitch but maintain tempo) is produced by a combination of -/// combining the two other controls. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ -// File revision : $Revision: 4 $ -// -// $Id: SoundTouch.cpp 143 2012-06-13 19:29:53Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include - -#include "SoundTouch.h" -#include "TDStretch.h" -#include "RateTransposer.h" -#include "cpu_detect.h" - -using namespace soundtouch; - -/// test if two floating point numbers are equal -#define TEST_FLOAT_EQUAL(a, b) (fabs(a - b) < 1e-10) - - -/// Print library version string for autoconf -extern "C" void soundtouch_ac_test() -{ - printf("SoundTouch Version: %s\n",SOUNDTOUCH_VERSION); -} - - -SoundTouch::SoundTouch() -{ - // Initialize rate transposer and tempo changer instances - - pRateTransposer = RateTransposer::newInstance(); - pTDStretch = TDStretch::newInstance(); - - setOutPipe(pTDStretch); - - rate = tempo = 0; - - virtualPitch = - virtualRate = - virtualTempo = 1.0; - - calcEffectiveRateAndTempo(); - - channels = 0; - bSrateSet = false; -} - - - -SoundTouch::~SoundTouch() -{ - delete pRateTransposer; - delete pTDStretch; -} - - - -/// Get SoundTouch library version string -const char *SoundTouch::getVersionString() -{ - static const char *_version = SOUNDTOUCH_VERSION; - - return _version; -} - - -/// Get SoundTouch library version Id -uint SoundTouch::getVersionId() -{ - return SOUNDTOUCH_VERSION_ID; -} - - -// Sets the number of channels, 1 = mono, 2 = stereo -void SoundTouch::setChannels(uint numChannels) -{ - if (numChannels != 1 && numChannels != 2) - { - ST_THROW_RT_ERROR("Illegal number of channels"); - } - channels = numChannels; - pRateTransposer->setChannels((int)numChannels); - pTDStretch->setChannels((int)numChannels); -} - - - -// Sets new rate control value. Normal rate = 1.0, smaller values -// represent slower rate, larger faster rates. -void SoundTouch::setRate(float newRate) -{ - virtualRate = newRate; - calcEffectiveRateAndTempo(); -} - - - -// Sets new rate control value as a difference in percents compared -// to the original rate (-50 .. +100 %) -void SoundTouch::setRateChange(float newRate) -{ - virtualRate = 1.0f + 0.01f * newRate; - calcEffectiveRateAndTempo(); -} - - - -// Sets new tempo control value. Normal tempo = 1.0, smaller values -// represent slower tempo, larger faster tempo. -void SoundTouch::setTempo(float newTempo) -{ - virtualTempo = newTempo; - calcEffectiveRateAndTempo(); -} - - - -// Sets new tempo control value as a difference in percents compared -// to the original tempo (-50 .. +100 %) -void SoundTouch::setTempoChange(float newTempo) -{ - virtualTempo = 1.0f + 0.01f * newTempo; - calcEffectiveRateAndTempo(); -} - - - -// Sets new pitch control value. Original pitch = 1.0, smaller values -// represent lower pitches, larger values higher pitch. -void SoundTouch::setPitch(float newPitch) -{ - virtualPitch = newPitch; - calcEffectiveRateAndTempo(); -} - - - -// Sets pitch change in octaves compared to the original pitch -// (-1.00 .. +1.00) -void SoundTouch::setPitchOctaves(float newPitch) -{ - virtualPitch = (float)exp(0.69314718056f * newPitch); - calcEffectiveRateAndTempo(); -} - - - -// Sets pitch change in semi-tones compared to the original pitch -// (-12 .. +12) -void SoundTouch::setPitchSemiTones(int newPitch) -{ - setPitchOctaves((float)newPitch / 12.0f); -} - - - -void SoundTouch::setPitchSemiTones(float newPitch) -{ - setPitchOctaves(newPitch / 12.0f); -} - - -// Calculates 'effective' rate and tempo values from the -// nominal control values. -void SoundTouch::calcEffectiveRateAndTempo() -{ - float oldTempo = tempo; - float oldRate = rate; - - tempo = virtualTempo / virtualPitch; - rate = virtualPitch * virtualRate; - - if (!TEST_FLOAT_EQUAL(rate,oldRate)) pRateTransposer->setRate(rate); - if (!TEST_FLOAT_EQUAL(tempo, oldTempo)) pTDStretch->setTempo(tempo); - -#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER - if (rate <= 1.0f) - { - if (output != pTDStretch) - { - FIFOSamplePipe *tempoOut; - - assert(output == pRateTransposer); - // move samples in the current output buffer to the output of pTDStretch - tempoOut = pTDStretch->getOutput(); - tempoOut->moveSamples(*output); - // move samples in pitch transposer's store buffer to tempo changer's input - pTDStretch->moveSamples(*pRateTransposer->getStore()); - - output = pTDStretch; - } - } - else -#endif - { - if (output != pRateTransposer) - { - FIFOSamplePipe *transOut; - - assert(output == pTDStretch); - // move samples in the current output buffer to the output of pRateTransposer - transOut = pRateTransposer->getOutput(); - transOut->moveSamples(*output); - // move samples in tempo changer's input to pitch transposer's input - pRateTransposer->moveSamples(*pTDStretch->getInput()); - - output = pRateTransposer; - } - } -} - - -// Sets sample rate. -void SoundTouch::setSampleRate(uint srate) -{ - bSrateSet = true; - // set sample rate, leave other tempo changer parameters as they are. - pTDStretch->setParameters((int)srate); -} - - -// Adds 'numSamples' pcs of samples from the 'samples' memory position into -// the input of the object. -void SoundTouch::putSamples(const SAMPLETYPE *samples, uint nSamples) -{ - if (bSrateSet == false) - { - ST_THROW_RT_ERROR("SoundTouch : Sample rate not defined"); - } - else if (channels == 0) - { - ST_THROW_RT_ERROR("SoundTouch : Number of channels not defined"); - } - - // Transpose the rate of the new samples if necessary - /* Bypass the nominal setting - can introduce a click in sound when tempo/pitch control crosses the nominal value... - if (rate == 1.0f) - { - // The rate value is same as the original, simply evaluate the tempo changer. - assert(output == pTDStretch); - if (pRateTransposer->isEmpty() == 0) - { - // yet flush the last samples in the pitch transposer buffer - // (may happen if 'rate' changes from a non-zero value to zero) - pTDStretch->moveSamples(*pRateTransposer); - } - pTDStretch->putSamples(samples, nSamples); - } - */ -#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER - else if (rate <= 1.0f) - { - // transpose the rate down, output the transposed sound to tempo changer buffer - assert(output == pTDStretch); - pRateTransposer->putSamples(samples, nSamples); - pTDStretch->moveSamples(*pRateTransposer); - } - else -#endif - { - // evaluate the tempo changer, then transpose the rate up, - assert(output == pRateTransposer); - pTDStretch->putSamples(samples, nSamples); - pRateTransposer->moveSamples(*pTDStretch); - } -} - - -// Flushes the last samples from the processing pipeline to the output. -// Clears also the internal processing buffers. -// -// Note: This function is meant for extracting the last samples of a sound -// stream. This function may introduce additional blank samples in the end -// of the sound stream, and thus it's not recommended to call this function -// in the middle of a sound stream. -void SoundTouch::flush() -{ - int i; - int nUnprocessed; - int nOut; - SAMPLETYPE buff[64*2]; // note: allocate 2*64 to cater 64 sample frames of stereo sound - - // check how many samples still await processing, and scale - // that by tempo & rate to get expected output sample count - nUnprocessed = numUnprocessedSamples(); - nUnprocessed = (int)((double)nUnprocessed / (tempo * rate) + 0.5); - - nOut = numSamples(); // ready samples currently in buffer ... - nOut += nUnprocessed; // ... and how many we expect there to be in the end - - memset(buff, 0, 64 * channels * sizeof(SAMPLETYPE)); - // "Push" the last active samples out from the processing pipeline by - // feeding blank samples into the processing pipeline until new, - // processed samples appear in the output (not however, more than - // 8ksamples in any case) - for (i = 0; i < 128; i ++) - { - putSamples(buff, 64); - if ((int)numSamples() >= nOut) - { - // Enough new samples have appeared into the output! - // As samples come from processing with bigger chunks, now truncate it - // back to maximum "nOut" samples to improve duration accuracy - adjustAmountOfSamples(nOut); - - // finish - break; - } - } - - // Clear working buffers - pRateTransposer->clear(); - pTDStretch->clearInput(); - // yet leave the 'tempoChanger' output intouched as that's where the - // flushed samples are! -} - - -// Changes a setting controlling the processing system behaviour. See the -// 'SETTING_...' defines for available setting ID's. -bool SoundTouch::setSetting(int settingId, int value) -{ - int sampleRate, sequenceMs, seekWindowMs, overlapMs; - - // read current tdstretch routine parameters - pTDStretch->getParameters(&sampleRate, &sequenceMs, &seekWindowMs, &overlapMs); - - switch (settingId) - { - case SETTING_USE_AA_FILTER : - // enables / disabless anti-alias filter - pRateTransposer->enableAAFilter((value != 0) ? true : false); - return true; - - case SETTING_AA_FILTER_LENGTH : - // sets anti-alias filter length - pRateTransposer->getAAFilter()->setLength(value); - return true; - - case SETTING_USE_QUICKSEEK : - // enables / disables tempo routine quick seeking algorithm - pTDStretch->enableQuickSeek((value != 0) ? true : false); - return true; - - case SETTING_SEQUENCE_MS: - // change time-stretch sequence duration parameter - pTDStretch->setParameters(sampleRate, value, seekWindowMs, overlapMs); - return true; - - case SETTING_SEEKWINDOW_MS: - // change time-stretch seek window length parameter - pTDStretch->setParameters(sampleRate, sequenceMs, value, overlapMs); - return true; - - case SETTING_OVERLAP_MS: - // change time-stretch overlap length parameter - pTDStretch->setParameters(sampleRate, sequenceMs, seekWindowMs, value); - return true; - - default : - return false; - } -} - - -// Reads a setting controlling the processing system behaviour. See the -// 'SETTING_...' defines for available setting ID's. -// -// Returns the setting value. -int SoundTouch::getSetting(int settingId) const -{ - int temp; - - switch (settingId) - { - case SETTING_USE_AA_FILTER : - return (uint)pRateTransposer->isAAFilterEnabled(); - - case SETTING_AA_FILTER_LENGTH : - return pRateTransposer->getAAFilter()->getLength(); - - case SETTING_USE_QUICKSEEK : - return (uint) pTDStretch->isQuickSeekEnabled(); - - case SETTING_SEQUENCE_MS: - pTDStretch->getParameters(NULL, &temp, NULL, NULL); - return temp; - - case SETTING_SEEKWINDOW_MS: - pTDStretch->getParameters(NULL, NULL, &temp, NULL); - return temp; - - case SETTING_OVERLAP_MS: - pTDStretch->getParameters(NULL, NULL, NULL, &temp); - return temp; - - case SETTING_NOMINAL_INPUT_SEQUENCE : - return pTDStretch->getInputSampleReq(); - - case SETTING_NOMINAL_OUTPUT_SEQUENCE : - return pTDStretch->getOutputBatchSize(); - - default : - return 0; - } -} - - -// Clears all the samples in the object's output and internal processing -// buffers. -void SoundTouch::clear() -{ - pRateTransposer->clear(); - pTDStretch->clear(); -} - - - -/// Returns number of samples currently unprocessed. -uint SoundTouch::numUnprocessedSamples() const -{ - FIFOSamplePipe * psp; - if (pTDStretch) - { - psp = pTDStretch->getInput(); - if (psp) - { - return psp->numSamples(); - } - } - return 0; -} diff --git a/media/libsoundtouch/src/SoundTouch.h b/media/libsoundtouch/src/SoundTouch.h deleted file mode 100644 index 42319ad8521..00000000000 --- a/media/libsoundtouch/src/SoundTouch.h +++ /dev/null @@ -1,277 +0,0 @@ -////////////////////////////////////////////////////////////////////////////// -/// -/// SoundTouch - main class for tempo/pitch/rate adjusting routines. -/// -/// Notes: -/// - Initialize the SoundTouch object instance by setting up the sound stream -/// parameters with functions 'setSampleRate' and 'setChannels', then set -/// desired tempo/pitch/rate settings with the corresponding functions. -/// -/// - The SoundTouch class behaves like a first-in-first-out pipeline: The -/// samples that are to be processed are fed into one of the pipe by calling -/// function 'putSamples', while the ready processed samples can be read -/// from the other end of the pipeline with function 'receiveSamples'. -/// -/// - The SoundTouch processing classes require certain sized 'batches' of -/// samples in order to process the sound. For this reason the classes buffer -/// incoming samples until there are enough of samples available for -/// processing, then they carry out the processing step and consequently -/// make the processed samples available for outputting. -/// -/// - For the above reason, the processing routines introduce a certain -/// 'latency' between the input and output, so that the samples input to -/// SoundTouch may not be immediately available in the output, and neither -/// the amount of outputtable samples may not immediately be in direct -/// relationship with the amount of previously input samples. -/// -/// - The tempo/pitch/rate control parameters can be altered during processing. -/// Please notice though that they aren't currently protected by semaphores, -/// so in multi-thread application external semaphore protection may be -/// required. -/// -/// - This class utilizes classes 'TDStretch' for tempo change (without modifying -/// pitch) and 'RateTransposer' for changing the playback rate (that is, both -/// tempo and pitch in the same ratio) of the sound. The third available control -/// 'pitch' (change pitch but maintain tempo) is produced by a combination of -/// combining the two other controls. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-04-04 12:47:28 -0700 (Wed, 04 Apr 2012) $ -// File revision : $Revision: 4 $ -// -// $Id: SoundTouch.h 141 2012-04-04 19:47:28Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef SoundTouch_H -#define SoundTouch_H - -#include "FIFOSamplePipe.h" -#include "STTypes.h" - -namespace soundtouch -{ - -/// Soundtouch library version string -#define SOUNDTOUCH_VERSION "1.7.0" - -/// SoundTouch library version id -#define SOUNDTOUCH_VERSION_ID (10700) - -// -// Available setting IDs for the 'setSetting' & 'get_setting' functions: - -/// Enable/disable anti-alias filter in pitch transposer (0 = disable) -#define SETTING_USE_AA_FILTER 0 - -/// Pitch transposer anti-alias filter length (8 .. 128 taps, default = 32) -#define SETTING_AA_FILTER_LENGTH 1 - -/// Enable/disable quick seeking algorithm in tempo changer routine -/// (enabling quick seeking lowers CPU utilization but causes a minor sound -/// quality compromising) -#define SETTING_USE_QUICKSEEK 2 - -/// Time-stretch algorithm single processing sequence length in milliseconds. This determines -/// to how long sequences the original sound is chopped in the time-stretch algorithm. -/// See "STTypes.h" or README for more information. -#define SETTING_SEQUENCE_MS 3 - -/// Time-stretch algorithm seeking window length in milliseconds for algorithm that finds the -/// best possible overlapping location. This determines from how wide window the algorithm -/// may look for an optimal joining location when mixing the sound sequences back together. -/// See "STTypes.h" or README for more information. -#define SETTING_SEEKWINDOW_MS 4 - -/// Time-stretch algorithm overlap length in milliseconds. When the chopped sound sequences -/// are mixed back together, to form a continuous sound stream, this parameter defines over -/// how long period the two consecutive sequences are let to overlap each other. -/// See "STTypes.h" or README for more information. -#define SETTING_OVERLAP_MS 5 - - -/// Call "getSetting" with this ID to query nominal average processing sequence -/// size in samples. This value tells approcimate value how many input samples -/// SoundTouch needs to gather before it does DSP processing run for the sample batch. -/// -/// Notices: -/// - This is read-only parameter, i.e. setSetting ignores this parameter -/// - Returned value is approximate average value, exact processing batch -/// size may wary from time to time -/// - This parameter value is not constant but may change depending on -/// tempo/pitch/rate/samplerate settings. -#define SETTING_NOMINAL_INPUT_SEQUENCE 6 - - -/// Call "getSetting" with this ID to query nominal average processing output -/// size in samples. This value tells approcimate value how many output samples -/// SoundTouch outputs once it does DSP processing run for a batch of input samples. -/// -/// Notices: -/// - This is read-only parameter, i.e. setSetting ignores this parameter -/// - Returned value is approximate average value, exact processing batch -/// size may wary from time to time -/// - This parameter value is not constant but may change depending on -/// tempo/pitch/rate/samplerate settings. -#define SETTING_NOMINAL_OUTPUT_SEQUENCE 7 - -class EXPORT SoundTouch : public FIFOProcessor -{ -private: - /// Rate transposer class instance - class RateTransposer *pRateTransposer; - - /// Time-stretch class instance - class TDStretch *pTDStretch; - - /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. - float virtualRate; - - /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. - float virtualTempo; - - /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. - float virtualPitch; - - /// Flag: Has sample rate been set? - bool bSrateSet; - - /// Calculates effective rate & tempo valuescfrom 'virtualRate', 'virtualTempo' and - /// 'virtualPitch' parameters. - void calcEffectiveRateAndTempo(); - -protected : - /// Number of channels - uint channels; - - /// Effective 'rate' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch' - float rate; - - /// Effective 'tempo' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch' - float tempo; - -public: - SoundTouch(); - virtual ~SoundTouch(); - - /// Get SoundTouch library version string - static const char *getVersionString(); - - /// Get SoundTouch library version Id - static uint getVersionId(); - - /// Sets new rate control value. Normal rate = 1.0, smaller values - /// represent slower rate, larger faster rates. - void setRate(float newRate); - - /// Sets new tempo control value. Normal tempo = 1.0, smaller values - /// represent slower tempo, larger faster tempo. - void setTempo(float newTempo); - - /// Sets new rate control value as a difference in percents compared - /// to the original rate (-50 .. +100 %) - void setRateChange(float newRate); - - /// Sets new tempo control value as a difference in percents compared - /// to the original tempo (-50 .. +100 %) - void setTempoChange(float newTempo); - - /// Sets new pitch control value. Original pitch = 1.0, smaller values - /// represent lower pitches, larger values higher pitch. - void setPitch(float newPitch); - - /// Sets pitch change in octaves compared to the original pitch - /// (-1.00 .. +1.00) - void setPitchOctaves(float newPitch); - - /// Sets pitch change in semi-tones compared to the original pitch - /// (-12 .. +12) - void setPitchSemiTones(int newPitch); - void setPitchSemiTones(float newPitch); - - /// Sets the number of channels, 1 = mono, 2 = stereo - void setChannels(uint numChannels); - - /// Sets sample rate. - void setSampleRate(uint srate); - - /// Flushes the last samples from the processing pipeline to the output. - /// Clears also the internal processing buffers. - // - /// Note: This function is meant for extracting the last samples of a sound - /// stream. This function may introduce additional blank samples in the end - /// of the sound stream, and thus it's not recommended to call this function - /// in the middle of a sound stream. - void flush(); - - /// Adds 'numSamples' pcs of samples from the 'samples' memory position into - /// the input of the object. Notice that sample rate _has_to_ be set before - /// calling this function, otherwise throws a runtime_error exception. - virtual void putSamples( - const SAMPLETYPE *samples, ///< Pointer to sample buffer. - uint numSamples ///< Number of samples in buffer. Notice - ///< that in case of stereo-sound a single sample - ///< contains data for both channels. - ); - - /// Clears all the samples in the object's output and internal processing - /// buffers. - virtual void clear(); - - /// Changes a setting controlling the processing system behaviour. See the - /// 'SETTING_...' defines for available setting ID's. - /// - /// \return 'true' if the setting was succesfully changed - bool setSetting(int settingId, ///< Setting ID number. see SETTING_... defines. - int value ///< New setting value. - ); - - /// Reads a setting controlling the processing system behaviour. See the - /// 'SETTING_...' defines for available setting ID's. - /// - /// \return the setting value. - int getSetting(int settingId ///< Setting ID number, see SETTING_... defines. - ) const; - - /// Returns number of samples currently unprocessed. - virtual uint numUnprocessedSamples() const; - - - /// Other handy functions that are implemented in the ancestor classes (see - /// classes 'FIFOProcessor' and 'FIFOSamplePipe') - /// - /// - receiveSamples() : Use this function to receive 'ready' processed samples from SoundTouch. - /// - numSamples() : Get number of 'ready' samples that can be received with - /// function 'receiveSamples()' - /// - isEmpty() : Returns nonzero if there aren't any 'ready' samples. - /// - clear() : Clears all samples from ready/processing buffers. -}; - -} -#endif diff --git a/media/libsoundtouch/src/TDStretch.cpp b/media/libsoundtouch/src/TDStretch.cpp deleted file mode 100644 index bec03ae2d6b..00000000000 --- a/media/libsoundtouch/src/TDStretch.cpp +++ /dev/null @@ -1,808 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo -/// while maintaining the original pitch by using a time domain WSOLA-like -/// method with several performance-increasing tweaks. -/// -/// Note : MMX optimized functions reside in a separate, platform-specific -/// file, e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ -// File revision : $Revision: 1.12 $ -// -// $Id: TDStretch.cpp 137 2012-04-01 19:49:30Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include - -#include "STTypes.h" -#include "cpu_detect.h" -#include "TDStretch.h" - -#include - -using namespace soundtouch; - -#define max(x, y) (((x) > (y)) ? (x) : (y)) - - -/***************************************************************************** - * - * Constant definitions - * - *****************************************************************************/ - -// Table for the hierarchical mixing position seeking algorithm -static const short _scanOffsets[5][24]={ - { 124, 186, 248, 310, 372, 434, 496, 558, 620, 682, 744, 806, - 868, 930, 992, 1054, 1116, 1178, 1240, 1302, 1364, 1426, 1488, 0}, - {-100, -75, -50, -25, 25, 50, 75, 100, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - { -20, -15, -10, -5, 5, 10, 15, 20, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - { -4, -3, -2, -1, 1, 2, 3, 4, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - { 121, 114, 97, 114, 98, 105, 108, 32, 104, 99, 117, 111, - 116, 100, 110, 117, 111, 115, 0, 0, 0, 0, 0, 0}}; - -/***************************************************************************** - * - * Implementation of the class 'TDStretch' - * - *****************************************************************************/ - - -TDStretch::TDStretch() : FIFOProcessor(&outputBuffer) -{ - bQuickSeek = false; - channels = 2; - - pMidBuffer = NULL; - pMidBufferUnaligned = NULL; - overlapLength = 0; - - bAutoSeqSetting = true; - bAutoSeekSetting = true; - -// outDebt = 0; - skipFract = 0; - - tempo = 1.0f; - setParameters(44100, DEFAULT_SEQUENCE_MS, DEFAULT_SEEKWINDOW_MS, DEFAULT_OVERLAP_MS); - setTempo(1.0f); - - clear(); -} - - - -TDStretch::~TDStretch() -{ - delete[] pMidBufferUnaligned; -} - - - -// Sets routine control parameters. These control are certain time constants -// defining how the sound is stretched to the desired duration. -// -// 'sampleRate' = sample rate of the sound -// 'sequenceMS' = one processing sequence length in milliseconds (default = 82 ms) -// 'seekwindowMS' = seeking window length for scanning the best overlapping -// position (default = 28 ms) -// 'overlapMS' = overlapping length (default = 12 ms) - -void TDStretch::setParameters(int aSampleRate, int aSequenceMS, - int aSeekWindowMS, int aOverlapMS) -{ - // accept only positive parameter values - if zero or negative, use old values instead - if (aSampleRate > 0) this->sampleRate = aSampleRate; - if (aOverlapMS > 0) this->overlapMs = aOverlapMS; - - if (aSequenceMS > 0) - { - this->sequenceMs = aSequenceMS; - bAutoSeqSetting = false; - } - else if (aSequenceMS == 0) - { - // if zero, use automatic setting - bAutoSeqSetting = true; - } - - if (aSeekWindowMS > 0) - { - this->seekWindowMs = aSeekWindowMS; - bAutoSeekSetting = false; - } - else if (aSeekWindowMS == 0) - { - // if zero, use automatic setting - bAutoSeekSetting = true; - } - - calcSeqParameters(); - - calculateOverlapLength(overlapMs); - - // set tempo to recalculate 'sampleReq' - setTempo(tempo); - -} - - - -/// Get routine control parameters, see setParameters() function. -/// Any of the parameters to this function can be NULL, in such case corresponding parameter -/// value isn't returned. -void TDStretch::getParameters(int *pSampleRate, int *pSequenceMs, int *pSeekWindowMs, int *pOverlapMs) const -{ - if (pSampleRate) - { - *pSampleRate = sampleRate; - } - - if (pSequenceMs) - { - *pSequenceMs = (bAutoSeqSetting) ? (USE_AUTO_SEQUENCE_LEN) : sequenceMs; - } - - if (pSeekWindowMs) - { - *pSeekWindowMs = (bAutoSeekSetting) ? (USE_AUTO_SEEKWINDOW_LEN) : seekWindowMs; - } - - if (pOverlapMs) - { - *pOverlapMs = overlapMs; - } -} - - -// Overlaps samples in 'midBuffer' with the samples in 'pInput' -void TDStretch::overlapMono(SAMPLETYPE *pOutput, const SAMPLETYPE *pInput) const -{ - int i; - SAMPLETYPE m1, m2; - - m1 = (SAMPLETYPE)0; - m2 = (SAMPLETYPE)overlapLength; - - for (i = 0; i < overlapLength ; i ++) - { - pOutput[i] = (pInput[i] * m1 + pMidBuffer[i] * m2 ) / overlapLength; - m1 += 1; - m2 -= 1; - } -} - - - -void TDStretch::clearMidBuffer() -{ - memset(pMidBuffer, 0, 2 * sizeof(SAMPLETYPE) * overlapLength); -} - - -void TDStretch::clearInput() -{ - inputBuffer.clear(); - clearMidBuffer(); -} - - -// Clears the sample buffers -void TDStretch::clear() -{ - outputBuffer.clear(); - clearInput(); -} - - - -// Enables/disables the quick position seeking algorithm. Zero to disable, nonzero -// to enable -void TDStretch::enableQuickSeek(bool enable) -{ - bQuickSeek = enable; -} - - -// Returns nonzero if the quick seeking algorithm is enabled. -bool TDStretch::isQuickSeekEnabled() const -{ - return bQuickSeek; -} - - -// Seeks for the optimal overlap-mixing position. -int TDStretch::seekBestOverlapPosition(const SAMPLETYPE *refPos) -{ - if (bQuickSeek) - { - return seekBestOverlapPositionQuick(refPos); - } - else - { - return seekBestOverlapPositionFull(refPos); - } -} - - -// Overlaps samples in 'midBuffer' with the samples in 'pInputBuffer' at position -// of 'ovlPos'. -inline void TDStretch::overlap(SAMPLETYPE *pOutput, const SAMPLETYPE *pInput, uint ovlPos) const -{ - if (channels == 2) - { - // stereo sound - overlapStereo(pOutput, pInput + 2 * ovlPos); - } else { - // mono sound. - overlapMono(pOutput, pInput + ovlPos); - } -} - - - -// Seeks for the optimal overlap-mixing position. The 'stereo' version of the -// routine -// -// The best position is determined as the position where the two overlapped -// sample sequences are 'most alike', in terms of the highest cross-correlation -// value over the overlapping period -int TDStretch::seekBestOverlapPositionFull(const SAMPLETYPE *refPos) -{ - int bestOffs; - double bestCorr, corr; - int i; - - bestCorr = FLT_MIN; - bestOffs = 0; - - // Scans for the best correlation value by testing each possible position - // over the permitted range. - for (i = 0; i < seekLength; i ++) - { - // Calculates correlation value for the mixing position corresponding - // to 'i' - corr = calcCrossCorr(refPos + channels * i, pMidBuffer); - // heuristic rule to slightly favour values close to mid of the range - double tmp = (double)(2 * i - seekLength) / (double)seekLength; - corr = ((corr + 0.1) * (1.0 - 0.25 * tmp * tmp)); - - // Checks for the highest correlation value - if (corr > bestCorr) - { - bestCorr = corr; - bestOffs = i; - } - } - // clear cross correlation routine state if necessary (is so e.g. in MMX routines). - clearCrossCorrState(); - - return bestOffs; -} - - -// Seeks for the optimal overlap-mixing position. The 'stereo' version of the -// routine -// -// The best position is determined as the position where the two overlapped -// sample sequences are 'most alike', in terms of the highest cross-correlation -// value over the overlapping period -int TDStretch::seekBestOverlapPositionQuick(const SAMPLETYPE *refPos) -{ - int j; - int bestOffs; - double bestCorr, corr; - int scanCount, corrOffset, tempOffset; - - bestCorr = FLT_MIN; - bestOffs = _scanOffsets[0][0]; - corrOffset = 0; - tempOffset = 0; - - // Scans for the best correlation value using four-pass hierarchical search. - // - // The look-up table 'scans' has hierarchical position adjusting steps. - // In first pass the routine searhes for the highest correlation with - // relatively coarse steps, then rescans the neighbourhood of the highest - // correlation with better resolution and so on. - for (scanCount = 0;scanCount < 4; scanCount ++) - { - j = 0; - while (_scanOffsets[scanCount][j]) - { - tempOffset = corrOffset + _scanOffsets[scanCount][j]; - if (tempOffset >= seekLength) break; - - // Calculates correlation value for the mixing position corresponding - // to 'tempOffset' - corr = (double)calcCrossCorr(refPos + channels * tempOffset, pMidBuffer); - // heuristic rule to slightly favour values close to mid of the range - double tmp = (double)(2 * tempOffset - seekLength) / seekLength; - corr = ((corr + 0.1) * (1.0 - 0.25 * tmp * tmp)); - - // Checks for the highest correlation value - if (corr > bestCorr) - { - bestCorr = corr; - bestOffs = tempOffset; - } - j ++; - } - corrOffset = bestOffs; - } - // clear cross correlation routine state if necessary (is so e.g. in MMX routines). - clearCrossCorrState(); - - return bestOffs; -} - - - -/// clear cross correlation routine state if necessary -void TDStretch::clearCrossCorrState() -{ - // default implementation is empty. -} - - -/// Calculates processing sequence length according to tempo setting -void TDStretch::calcSeqParameters() -{ - // Adjust tempo param according to tempo, so that variating processing sequence length is used - // at varius tempo settings, between the given low...top limits - #define AUTOSEQ_TEMPO_LOW 0.5 // auto setting low tempo range (-50%) - #define AUTOSEQ_TEMPO_TOP 2.0 // auto setting top tempo range (+100%) - - // sequence-ms setting values at above low & top tempo - #define AUTOSEQ_AT_MIN 125.0 - #define AUTOSEQ_AT_MAX 50.0 - #define AUTOSEQ_K ((AUTOSEQ_AT_MAX - AUTOSEQ_AT_MIN) / (AUTOSEQ_TEMPO_TOP - AUTOSEQ_TEMPO_LOW)) - #define AUTOSEQ_C (AUTOSEQ_AT_MIN - (AUTOSEQ_K) * (AUTOSEQ_TEMPO_LOW)) - - // seek-window-ms setting values at above low & top tempo - #define AUTOSEEK_AT_MIN 25.0 - #define AUTOSEEK_AT_MAX 15.0 - #define AUTOSEEK_K ((AUTOSEEK_AT_MAX - AUTOSEEK_AT_MIN) / (AUTOSEQ_TEMPO_TOP - AUTOSEQ_TEMPO_LOW)) - #define AUTOSEEK_C (AUTOSEEK_AT_MIN - (AUTOSEEK_K) * (AUTOSEQ_TEMPO_LOW)) - - #define CHECK_LIMITS(x, mi, ma) (((x) < (mi)) ? (mi) : (((x) > (ma)) ? (ma) : (x))) - - double seq, seek; - - if (bAutoSeqSetting) - { - seq = AUTOSEQ_C + AUTOSEQ_K * tempo; - seq = CHECK_LIMITS(seq, AUTOSEQ_AT_MAX, AUTOSEQ_AT_MIN); - sequenceMs = (int)(seq + 0.5); - } - - if (bAutoSeekSetting) - { - seek = AUTOSEEK_C + AUTOSEEK_K * tempo; - seek = CHECK_LIMITS(seek, AUTOSEEK_AT_MAX, AUTOSEEK_AT_MIN); - seekWindowMs = (int)(seek + 0.5); - } - - // Update seek window lengths - seekWindowLength = (sampleRate * sequenceMs) / 1000; - if (seekWindowLength < 2 * overlapLength) - { - seekWindowLength = 2 * overlapLength; - } - seekLength = (sampleRate * seekWindowMs) / 1000; -} - - - -// Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower -// tempo, larger faster tempo. -void TDStretch::setTempo(float newTempo) -{ - int intskip; - - tempo = newTempo; - - // Calculate new sequence duration - calcSeqParameters(); - - // Calculate ideal skip length (according to tempo value) - nominalSkip = tempo * (seekWindowLength - overlapLength); - intskip = (int)(nominalSkip + 0.5f); - - // Calculate how many samples are needed in the 'inputBuffer' to - // process another batch of samples - //sampleReq = max(intskip + overlapLength, seekWindowLength) + seekLength / 2; - sampleReq = max(intskip + overlapLength, seekWindowLength) + seekLength; -} - - - -// Sets the number of channels, 1 = mono, 2 = stereo -void TDStretch::setChannels(int numChannels) -{ - assert(numChannels > 0); - if (channels == numChannels) return; - assert(numChannels == 1 || numChannels == 2); - - channels = numChannels; - inputBuffer.setChannels(channels); - outputBuffer.setChannels(channels); -} - - -// nominal tempo, no need for processing, just pass the samples through -// to outputBuffer -/* -void TDStretch::processNominalTempo() -{ - assert(tempo == 1.0f); - - if (bMidBufferDirty) - { - // If there are samples in pMidBuffer waiting for overlapping, - // do a single sliding overlapping with them in order to prevent a - // clicking distortion in the output sound - if (inputBuffer.numSamples() < overlapLength) - { - // wait until we've got overlapLength input samples - return; - } - // Mix the samples in the beginning of 'inputBuffer' with the - // samples in 'midBuffer' using sliding overlapping - overlap(outputBuffer.ptrEnd(overlapLength), inputBuffer.ptrBegin(), 0); - outputBuffer.putSamples(overlapLength); - inputBuffer.receiveSamples(overlapLength); - clearMidBuffer(); - // now we've caught the nominal sample flow and may switch to - // bypass mode - } - - // Simply bypass samples from input to output - outputBuffer.moveSamples(inputBuffer); -} -*/ - -#include - -// Processes as many processing frames of the samples 'inputBuffer', store -// the result into 'outputBuffer' -void TDStretch::processSamples() -{ - int ovlSkip, offset; - int temp; - - /* Removed this small optimization - can introduce a click to sound when tempo setting - crosses the nominal value - if (tempo == 1.0f) - { - // tempo not changed from the original, so bypass the processing - processNominalTempo(); - return; - } - */ - - // Process samples as long as there are enough samples in 'inputBuffer' - // to form a processing frame. - while ((int)inputBuffer.numSamples() >= sampleReq) - { - // If tempo differs from the normal ('SCALE'), scan for the best overlapping - // position - offset = seekBestOverlapPosition(inputBuffer.ptrBegin()); - - // Mix the samples in the 'inputBuffer' at position of 'offset' with the - // samples in 'midBuffer' using sliding overlapping - // ... first partially overlap with the end of the previous sequence - // (that's in 'midBuffer') - overlap(outputBuffer.ptrEnd((uint)overlapLength), inputBuffer.ptrBegin(), (uint)offset); - outputBuffer.putSamples((uint)overlapLength); - - // ... then copy sequence samples from 'inputBuffer' to output: - - // length of sequence - temp = (seekWindowLength - 2 * overlapLength); - - // crosscheck that we don't have buffer overflow... - if ((int)inputBuffer.numSamples() < (offset + temp + overlapLength * 2)) - { - continue; // just in case, shouldn't really happen - } - - outputBuffer.putSamples(inputBuffer.ptrBegin() + channels * (offset + overlapLength), (uint)temp); - - // Copies the end of the current sequence from 'inputBuffer' to - // 'midBuffer' for being mixed with the beginning of the next - // processing sequence and so on - assert((offset + temp + overlapLength * 2) <= (int)inputBuffer.numSamples()); - memcpy(pMidBuffer, inputBuffer.ptrBegin() + channels * (offset + temp + overlapLength), - channels * sizeof(SAMPLETYPE) * overlapLength); - - // Remove the processed samples from the input buffer. Update - // the difference between integer & nominal skip step to 'skipFract' - // in order to prevent the error from accumulating over time. - skipFract += nominalSkip; // real skip size - ovlSkip = (int)skipFract; // rounded to integer skip - skipFract -= ovlSkip; // maintain the fraction part, i.e. real vs. integer skip - inputBuffer.receiveSamples((uint)ovlSkip); - } -} - - -// Adds 'numsamples' pcs of samples from the 'samples' memory position into -// the input of the object. -void TDStretch::putSamples(const SAMPLETYPE *samples, uint nSamples) -{ - // Add the samples into the input buffer - inputBuffer.putSamples(samples, nSamples); - // Process the samples in input buffer - processSamples(); -} - - - -/// Set new overlap length parameter & reallocate RefMidBuffer if necessary. -void TDStretch::acceptNewOverlapLength(int newOverlapLength) -{ - int prevOvl; - - assert(newOverlapLength >= 0); - prevOvl = overlapLength; - overlapLength = newOverlapLength; - - if (overlapLength > prevOvl) - { - delete[] pMidBufferUnaligned; - - pMidBufferUnaligned = new SAMPLETYPE[overlapLength * 2 + 16 / sizeof(SAMPLETYPE)]; - // ensure that 'pMidBuffer' is aligned to 16 byte boundary for efficiency - pMidBuffer = (SAMPLETYPE *)((((ulong)pMidBufferUnaligned) + 15) & (ulong)-16); - - clearMidBuffer(); - } -} - - -// Operator 'new' is overloaded so that it automatically creates a suitable instance -// depending on if we've a MMX/SSE/etc-capable CPU available or not. -void * TDStretch::operator new(size_t s) -{ - // Notice! don't use "new TDStretch" directly, use "newInstance" to create a new instance instead! - ST_THROW_RT_ERROR("Error in TDStretch::new: Don't use 'new TDStretch' directly, use 'newInstance' member instead!"); - return newInstance(); -} - - -TDStretch * TDStretch::newInstance() -{ - uint uExtensions; - - uExtensions = detectCPUextensions(); - - // Check if MMX/SSE instruction set extensions supported by CPU - -#ifdef SOUNDTOUCH_ALLOW_MMX - // MMX routines available only with integer sample types - if (uExtensions & SUPPORT_MMX) - { - return ::new TDStretchMMX; - } - else -#endif // SOUNDTOUCH_ALLOW_MMX - - -#ifdef SOUNDTOUCH_ALLOW_SSE - if (uExtensions & SUPPORT_SSE) - { - // SSE support - return ::new TDStretchSSE; - } - else -#endif // SOUNDTOUCH_ALLOW_SSE - - { - // ISA optimizations not supported, use plain C version - return ::new TDStretch; - } -} - - -////////////////////////////////////////////////////////////////////////////// -// -// Integer arithmetics specific algorithm implementations. -// -////////////////////////////////////////////////////////////////////////////// - -#ifdef SOUNDTOUCH_INTEGER_SAMPLES - -// Overlaps samples in 'midBuffer' with the samples in 'input'. The 'Stereo' -// version of the routine. -void TDStretch::overlapStereo(short *poutput, const short *input) const -{ - int i; - short temp; - int cnt2; - - for (i = 0; i < overlapLength ; i ++) - { - temp = (short)(overlapLength - i); - cnt2 = 2 * i; - poutput[cnt2] = (input[cnt2] * i + pMidBuffer[cnt2] * temp ) / overlapLength; - poutput[cnt2 + 1] = (input[cnt2 + 1] * i + pMidBuffer[cnt2 + 1] * temp ) / overlapLength; - } -} - -// Calculates the x having the closest 2^x value for the given value -static int _getClosest2Power(double value) -{ - return (int)(log(value) / log(2.0) + 0.5); -} - - -/// Calculates overlap period length in samples. -/// Integer version rounds overlap length to closest power of 2 -/// for a divide scaling operation. -void TDStretch::calculateOverlapLength(int aoverlapMs) -{ - int newOvl; - - assert(aoverlapMs >= 0); - - // calculate overlap length so that it's power of 2 - thus it's easy to do - // integer division by right-shifting. Term "-1" at end is to account for - // the extra most significatnt bit left unused in result by signed multiplication - overlapDividerBits = _getClosest2Power((sampleRate * aoverlapMs) / 1000.0) - 1; - if (overlapDividerBits > 9) overlapDividerBits = 9; - if (overlapDividerBits < 3) overlapDividerBits = 3; - newOvl = (int)pow(2.0, (int)overlapDividerBits + 1); // +1 => account for -1 above - - acceptNewOverlapLength(newOvl); - - // calculate sloping divider so that crosscorrelation operation won't - // overflow 32-bit register. Max. sum of the crosscorrelation sum without - // divider would be 2^30*(N^3-N)/3, where N = overlap length - slopingDivider = (newOvl * newOvl - 1) / 3; -} - - -double TDStretch::calcCrossCorr(const short *mixingPos, const short *compare) const -{ - long corr; - long norm; - int i; - - corr = norm = 0; - // Same routine for stereo and mono. For stereo, unroll loop for better - // efficiency and gives slightly better resolution against rounding. - // For mono it same routine, just unrolls loop by factor of 4 - for (i = 0; i < channels * overlapLength; i += 4) - { - corr += (mixingPos[i] * compare[i] + - mixingPos[i + 1] * compare[i + 1] + - mixingPos[i + 2] * compare[i + 2] + - mixingPos[i + 3] * compare[i + 3]) >> overlapDividerBits; - norm += (mixingPos[i] * mixingPos[i] + - mixingPos[i + 1] * mixingPos[i + 1] + - mixingPos[i + 2] * mixingPos[i + 2] + - mixingPos[i + 3] * mixingPos[i + 3]) >> overlapDividerBits; - } - - // Normalize result by dividing by sqrt(norm) - this step is easiest - // done using floating point operation - if (norm == 0) norm = 1; // to avoid div by zero - return (double)corr / sqrt((double)norm); -} - -#endif // SOUNDTOUCH_INTEGER_SAMPLES - -////////////////////////////////////////////////////////////////////////////// -// -// Floating point arithmetics specific algorithm implementations. -// - -#ifdef SOUNDTOUCH_FLOAT_SAMPLES - -// Overlaps samples in 'midBuffer' with the samples in 'pInput' -void TDStretch::overlapStereo(float *pOutput, const float *pInput) const -{ - int i; - float fScale; - float f1; - float f2; - - fScale = 1.0f / (float)overlapLength; - - f1 = 0; - f2 = 1.0f; - - for (i = 0; i < 2 * (int)overlapLength ; i += 2) - { - pOutput[i + 0] = pInput[i + 0] * f1 + pMidBuffer[i + 0] * f2; - pOutput[i + 1] = pInput[i + 1] * f1 + pMidBuffer[i + 1] * f2; - - f1 += fScale; - f2 -= fScale; - } -} - - -/// Calculates overlapInMsec period length in samples. -void TDStretch::calculateOverlapLength(int overlapInMsec) -{ - int newOvl; - - assert(overlapInMsec >= 0); - newOvl = (sampleRate * overlapInMsec) / 1000; - if (newOvl < 16) newOvl = 16; - - // must be divisible by 8 - newOvl -= newOvl % 8; - - acceptNewOverlapLength(newOvl); -} - - -double TDStretch::calcCrossCorr(const float *mixingPos, const float *compare) const -{ - double corr; - double norm; - int i; - - corr = norm = 0; - // Same routine for stereo and mono. For Stereo, unroll by factor of 2. - // For mono it's same routine yet unrollsd by factor of 4. - for (i = 0; i < channels * overlapLength; i += 4) - { - corr += mixingPos[i] * compare[i] + - mixingPos[i + 1] * compare[i + 1]; - - norm += mixingPos[i] * mixingPos[i] + - mixingPos[i + 1] * mixingPos[i + 1]; - - // unroll the loop for better CPU efficiency: - corr += mixingPos[i + 2] * compare[i + 2] + - mixingPos[i + 3] * compare[i + 3]; - - norm += mixingPos[i + 2] * mixingPos[i + 2] + - mixingPos[i + 3] * mixingPos[i + 3]; - } - - if (norm < 1e-9) norm = 1.0; // to avoid div by zero - return corr / sqrt(norm); -} - -#endif // SOUNDTOUCH_FLOAT_SAMPLES diff --git a/media/libsoundtouch/src/TDStretch.h b/media/libsoundtouch/src/TDStretch.h deleted file mode 100644 index fcd0767e1f9..00000000000 --- a/media/libsoundtouch/src/TDStretch.h +++ /dev/null @@ -1,268 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo -/// while maintaining the original pitch by using a time domain WSOLA-like method -/// with several performance-increasing tweaks. -/// -/// Note : MMX/SSE optimized functions reside in separate, platform-specific files -/// 'mmx_optimized.cpp' and 'sse_optimized.cpp' -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ -// File revision : $Revision: 4 $ -// -// $Id: TDStretch.h 137 2012-04-01 19:49:30Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef TDStretch_H -#define TDStretch_H - -#include -#include "STTypes.h" -#include "RateTransposer.h" -#include "FIFOSamplePipe.h" - -namespace soundtouch -{ - -/// Default values for sound processing parameters: -/// Notice that the default parameters are tuned for contemporary popular music -/// processing. For speech processing applications these parameters suit better: -/// #define DEFAULT_SEQUENCE_MS 40 -/// #define DEFAULT_SEEKWINDOW_MS 15 -/// #define DEFAULT_OVERLAP_MS 8 -/// - -/// Default length of a single processing sequence, in milliseconds. This determines to how -/// long sequences the original sound is chopped in the time-stretch algorithm. -/// -/// The larger this value is, the lesser sequences are used in processing. In principle -/// a bigger value sounds better when slowing down tempo, but worse when increasing tempo -/// and vice versa. -/// -/// Increasing this value reduces computational burden & vice versa. -//#define DEFAULT_SEQUENCE_MS 40 -#define DEFAULT_SEQUENCE_MS USE_AUTO_SEQUENCE_LEN - -/// Giving this value for the sequence length sets automatic parameter value -/// according to tempo setting (recommended) -#define USE_AUTO_SEQUENCE_LEN 0 - -/// Seeking window default length in milliseconds for algorithm that finds the best possible -/// overlapping location. This determines from how wide window the algorithm may look for an -/// optimal joining location when mixing the sound sequences back together. -/// -/// The bigger this window setting is, the higher the possibility to find a better mixing -/// position will become, but at the same time large values may cause a "drifting" artifact -/// because consequent sequences will be taken at more uneven intervals. -/// -/// If there's a disturbing artifact that sounds as if a constant frequency was drifting -/// around, try reducing this setting. -/// -/// Increasing this value increases computational burden & vice versa. -//#define DEFAULT_SEEKWINDOW_MS 15 -#define DEFAULT_SEEKWINDOW_MS USE_AUTO_SEEKWINDOW_LEN - -/// Giving this value for the seek window length sets automatic parameter value -/// according to tempo setting (recommended) -#define USE_AUTO_SEEKWINDOW_LEN 0 - -/// Overlap length in milliseconds. When the chopped sound sequences are mixed back together, -/// to form a continuous sound stream, this parameter defines over how long period the two -/// consecutive sequences are let to overlap each other. -/// -/// This shouldn't be that critical parameter. If you reduce the DEFAULT_SEQUENCE_MS setting -/// by a large amount, you might wish to try a smaller value on this. -/// -/// Increasing this value increases computational burden & vice versa. -#define DEFAULT_OVERLAP_MS 8 - - -/// Class that does the time-stretch (tempo change) effect for the processed -/// sound. -class TDStretch : public FIFOProcessor -{ -protected: - int channels; - int sampleReq; - float tempo; - - SAMPLETYPE *pMidBuffer; - SAMPLETYPE *pMidBufferUnaligned; - int overlapLength; - int seekLength; - int seekWindowLength; - int overlapDividerBits; - int slopingDivider; - float nominalSkip; - float skipFract; - FIFOSampleBuffer outputBuffer; - FIFOSampleBuffer inputBuffer; - bool bQuickSeek; - - int sampleRate; - int sequenceMs; - int seekWindowMs; - int overlapMs; - bool bAutoSeqSetting; - bool bAutoSeekSetting; - - void acceptNewOverlapLength(int newOverlapLength); - - virtual void clearCrossCorrState(); - void calculateOverlapLength(int overlapMs); - - virtual double calcCrossCorr(const SAMPLETYPE *mixingPos, const SAMPLETYPE *compare) const; - - virtual int seekBestOverlapPositionFull(const SAMPLETYPE *refPos); - virtual int seekBestOverlapPositionQuick(const SAMPLETYPE *refPos); - int seekBestOverlapPosition(const SAMPLETYPE *refPos); - - virtual void overlapStereo(SAMPLETYPE *output, const SAMPLETYPE *input) const; - virtual void overlapMono(SAMPLETYPE *output, const SAMPLETYPE *input) const; - - void clearMidBuffer(); - void overlap(SAMPLETYPE *output, const SAMPLETYPE *input, uint ovlPos) const; - - void calcSeqParameters(); - - /// Changes the tempo of the given sound samples. - /// Returns amount of samples returned in the "output" buffer. - /// The maximum amount of samples that can be returned at a time is set by - /// the 'set_returnBuffer_size' function. - void processSamples(); - -public: - TDStretch(); - virtual ~TDStretch(); - - /// Operator 'new' is overloaded so that it automatically creates a suitable instance - /// depending on if we've a MMX/SSE/etc-capable CPU available or not. - static void *operator new(size_t s); - - /// Use this function instead of "new" operator to create a new instance of this class. - /// This function automatically chooses a correct feature set depending on if the CPU - /// supports MMX/SSE/etc extensions. - static TDStretch *newInstance(); - - /// Returns the output buffer object - FIFOSamplePipe *getOutput() { return &outputBuffer; }; - - /// Returns the input buffer object - FIFOSamplePipe *getInput() { return &inputBuffer; }; - - /// Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower - /// tempo, larger faster tempo. - void setTempo(float newTempo); - - /// Returns nonzero if there aren't any samples available for outputting. - virtual void clear(); - - /// Clears the input buffer - void clearInput(); - - /// Sets the number of channels, 1 = mono, 2 = stereo - void setChannels(int numChannels); - - /// Enables/disables the quick position seeking algorithm. Zero to disable, - /// nonzero to enable - void enableQuickSeek(bool enable); - - /// Returns nonzero if the quick seeking algorithm is enabled. - bool isQuickSeekEnabled() const; - - /// Sets routine control parameters. These control are certain time constants - /// defining how the sound is stretched to the desired duration. - // - /// 'sampleRate' = sample rate of the sound - /// 'sequenceMS' = one processing sequence length in milliseconds - /// 'seekwindowMS' = seeking window length for scanning the best overlapping - /// position - /// 'overlapMS' = overlapping length - void setParameters(int sampleRate, ///< Samplerate of sound being processed (Hz) - int sequenceMS = -1, ///< Single processing sequence length (ms) - int seekwindowMS = -1, ///< Offset seeking window length (ms) - int overlapMS = -1 ///< Sequence overlapping length (ms) - ); - - /// Get routine control parameters, see setParameters() function. - /// Any of the parameters to this function can be NULL, in such case corresponding parameter - /// value isn't returned. - void getParameters(int *pSampleRate, int *pSequenceMs, int *pSeekWindowMs, int *pOverlapMs) const; - - /// Adds 'numsamples' pcs of samples from the 'samples' memory position into - /// the input of the object. - virtual void putSamples( - const SAMPLETYPE *samples, ///< Input sample data - uint numSamples ///< Number of samples in 'samples' so that one sample - ///< contains both channels if stereo - ); - - /// return nominal input sample requirement for triggering a processing batch - int getInputSampleReq() const - { - return (int)(nominalSkip + 0.5); - } - - /// return nominal output sample amount when running a processing batch - int getOutputBatchSize() const - { - return seekWindowLength - overlapLength; - } -}; - - - -// Implementation-specific class declarations: - -#ifdef SOUNDTOUCH_ALLOW_MMX - /// Class that implements MMX optimized routines for 16bit integer samples type. - class TDStretchMMX : public TDStretch - { - protected: - double calcCrossCorr(const short *mixingPos, const short *compare) const; - virtual void overlapStereo(short *output, const short *input) const; - virtual void clearCrossCorrState(); - }; -#endif /// SOUNDTOUCH_ALLOW_MMX - - -#ifdef SOUNDTOUCH_ALLOW_SSE - /// Class that implements SSE optimized routines for floating point samples type. - class TDStretchSSE : public TDStretch - { - protected: - double calcCrossCorr(const float *mixingPos, const float *compare) const; - }; - -#endif /// SOUNDTOUCH_ALLOW_SSE - -} -#endif /// TDStretch_H diff --git a/media/libsoundtouch/src/cpu_detect.h b/media/libsoundtouch/src/cpu_detect.h deleted file mode 100644 index b55569060fc..00000000000 --- a/media/libsoundtouch/src/cpu_detect.h +++ /dev/null @@ -1,62 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// A header file for detecting the Intel MMX instructions set extension. -/// -/// Please see 'mmx_win.cpp', 'mmx_cpp.cpp' and 'mmx_non_x86.cpp' for the -/// routine implementations for x86 Windows, x86 gnu version and non-x86 -/// platforms, respectively. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2008-02-10 08:26:55 -0800 (Sun, 10 Feb 2008) $ -// File revision : $Revision: 4 $ -// -// $Id: cpu_detect.h 11 2008-02-10 16:26:55Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef _CPU_DETECT_H_ -#define _CPU_DETECT_H_ - -#include "STTypes.h" - -#define SUPPORT_MMX 0x0001 -#define SUPPORT_3DNOW 0x0002 -#define SUPPORT_ALTIVEC 0x0004 -#define SUPPORT_SSE 0x0008 -#define SUPPORT_SSE2 0x0010 - -/// Checks which instruction set extensions are supported by the CPU. -/// -/// \return A bitmask of supported extensions, see SUPPORT_... defines. -uint detectCPUextensions(void); - -/// Disables given set of instruction extensions. See SUPPORT_... defines. -void disableExtensions(uint wDisableMask); - -#endif // _CPU_DETECT_H_ diff --git a/media/libsoundtouch/src/cpu_detect_x86.cpp b/media/libsoundtouch/src/cpu_detect_x86.cpp deleted file mode 100644 index d15c6bbace6..00000000000 --- a/media/libsoundtouch/src/cpu_detect_x86.cpp +++ /dev/null @@ -1,144 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// Generic version of the x86 CPU extension detection routine. -/// -/// This file is for GNU & other non-Windows compilers, see 'cpu_detect_x86_win.cpp' -/// for the Microsoft compiler version. -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-04-01 13:00:09 -0700 (Sun, 01 Apr 2012) $ -// File revision : $Revision: 4 $ -// -// $Id: cpu_detect_x86.cpp 138 2012-04-01 20:00:09Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#include "cpu_detect.h" -#include "STTypes.h" - -#if defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) - #if defined(__GNUC__) - // gcc and clang - #include "cpuid.h" - #endif - - #if defined(_M_IX86) - // windows - #include - #endif - // If we still don't have the macros, define them (Windows, MacOS) - #ifndef bit_MMX - #define bit_MMX (1 << 23) - #endif - #ifndef bit_SSE - #define bit_SSE (1 << 25) - #endif - #ifndef bit_SSE2 - #define bit_SSE2 (1 << 26) - #endif -#endif - - -////////////////////////////////////////////////////////////////////////////// -// -// processor instructions extension detection routines -// -////////////////////////////////////////////////////////////////////////////// - -// Flag variable indicating whick ISA extensions are disabled (for debugging) -static uint _dwDisabledISA = 0x00; // 0xffffffff; //<- use this to disable all extensions - -// Disables given set of instruction extensions. See SUPPORT_... defines. -void disableExtensions(uint dwDisableMask) -{ - _dwDisabledISA = dwDisableMask; -} - - - -/// Checks which instruction set extensions are supported by the CPU. -uint detectCPUextensions(void) -{ -/// If building for a 64bit system (no Itanium) and the user wants optimizations. -/// Return the OR of SUPPORT_{MMX,SSE,SSE2}. 11001 or 0x19. -/// Keep the _dwDisabledISA test (2 more operations, could be eliminated). -#if ((defined(__GNUC__) && defined(__x86_64__)) \ - || defined(_M_X64)) \ - && defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) - return 0x19 & ~_dwDisabledISA; - -/// If building for a 32bit system and the user wants optimizations. -/// Keep the _dwDisabledISA test (2 more operations, could be eliminated). -#elif ((defined(__GNUC__) && defined(__i386__)) \ - || defined(_M_IX86)) \ - && defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) - - if (_dwDisabledISA == 0xffffffff) return 0; - - uint res = 0; - -#if defined(__GNUC__) - // GCC version of cpuid. Requires GCC 4.3.0 or later for __cpuid intrinsic support. - uint eax, ebx, ecx, edx; // unsigned int is the standard type. uint is defined by the compiler and not guaranteed to be portable. - - // Check if no cpuid support. - if (!__get_cpuid (1, &eax, &ebx, &ecx, &edx)) return 0; // always disable extensions. - - if (edx & bit_MMX) res = res | SUPPORT_MMX; - if (edx & bit_SSE) res = res | SUPPORT_SSE; - if (edx & bit_SSE2) res = res | SUPPORT_SSE2; - -#else - // Window / VS version of cpuid. Notice that Visual Studio 2005 or later required - // for __cpuid intrinsic support. - int reg[4] = {-1}; - - // Check if no cpuid support. - __cpuid(reg,0); - if ((unsigned int)reg[0] == 0) return 0; // always disable extensions. - - __cpuid(reg,1); - if ((unsigned int)reg[3] & bit_MMX) res = res | SUPPORT_MMX; - if ((unsigned int)reg[3] & bit_SSE) res = res | SUPPORT_SSE; - if ((unsigned int)reg[3] & bit_SSE2) res = res | SUPPORT_SSE2; - -#endif - - return res & ~_dwDisabledISA; - -#else - -/// One of these is true: -/// 1) We don't want optimizations. -/// 2) Using an unsupported compiler. -/// 3) Running on a non-x86 platform. - return 0; - -#endif -} diff --git a/media/libsoundtouch/src/mmx_optimized.cpp b/media/libsoundtouch/src/mmx_optimized.cpp deleted file mode 100644 index 7cb65edd3e3..00000000000 --- a/media/libsoundtouch/src/mmx_optimized.cpp +++ /dev/null @@ -1,317 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// MMX optimized routines. All MMX optimized functions have been gathered into -/// this single source code file, regardless to their class or original source -/// code file, in order to ease porting the library to other compiler and -/// processor platforms. -/// -/// The MMX-optimizations are programmed using MMX compiler intrinsics that -/// are supported both by Microsoft Visual C++ and GCC compilers, so this file -/// should compile with both toolsets. -/// -/// NOTICE: If using Visual Studio 6.0, you'll need to install the "Visual C++ -/// 6.0 processor pack" update to support compiler intrinsic syntax. The update -/// is available for download at Microsoft Developers Network, see here: -/// http://msdn.microsoft.com/en-us/vstudio/aa718349.aspx -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ -// File revision : $Revision: 4 $ -// -// $Id: mmx_optimized.cpp 137 2012-04-01 19:49:30Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#include "STTypes.h" - -#ifdef SOUNDTOUCH_ALLOW_MMX -// MMX routines available only with integer sample type - -using namespace soundtouch; - -////////////////////////////////////////////////////////////////////////////// -// -// implementation of MMX optimized functions of class 'TDStretchMMX' -// -////////////////////////////////////////////////////////////////////////////// - -#include "TDStretch.h" -#include -#include -#include - - -// Calculates cross correlation of two buffers -double TDStretchMMX::calcCrossCorr(const short *pV1, const short *pV2) const -{ - const __m64 *pVec1, *pVec2; - __m64 shifter; - __m64 accu, normaccu; - long corr, norm; - int i; - - pVec1 = (__m64*)pV1; - pVec2 = (__m64*)pV2; - - shifter = _m_from_int(overlapDividerBits); - normaccu = accu = _mm_setzero_si64(); - - // Process 4 parallel sets of 2 * stereo samples or 4 * mono samples - // during each round for improved CPU-level parallellization. - for (i = 0; i < channels * overlapLength / 16; i ++) - { - __m64 temp, temp2; - - // dictionary of instructions: - // _m_pmaddwd : 4*16bit multiply-add, resulting two 32bits = [a0*b0+a1*b1 ; a2*b2+a3*b3] - // _mm_add_pi32 : 2*32bit add - // _m_psrad : 32bit right-shift - - temp = _mm_add_pi32(_mm_madd_pi16(pVec1[0], pVec2[0]), - _mm_madd_pi16(pVec1[1], pVec2[1])); - temp2 = _mm_add_pi32(_mm_madd_pi16(pVec1[0], pVec1[0]), - _mm_madd_pi16(pVec1[1], pVec1[1])); - accu = _mm_add_pi32(accu, _mm_sra_pi32(temp, shifter)); - normaccu = _mm_add_pi32(normaccu, _mm_sra_pi32(temp2, shifter)); - - temp = _mm_add_pi32(_mm_madd_pi16(pVec1[2], pVec2[2]), - _mm_madd_pi16(pVec1[3], pVec2[3])); - temp2 = _mm_add_pi32(_mm_madd_pi16(pVec1[2], pVec1[2]), - _mm_madd_pi16(pVec1[3], pVec1[3])); - accu = _mm_add_pi32(accu, _mm_sra_pi32(temp, shifter)); - normaccu = _mm_add_pi32(normaccu, _mm_sra_pi32(temp2, shifter)); - - pVec1 += 4; - pVec2 += 4; - } - - // copy hi-dword of mm0 to lo-dword of mm1, then sum mmo+mm1 - // and finally store the result into the variable "corr" - - accu = _mm_add_pi32(accu, _mm_srli_si64(accu, 32)); - corr = _m_to_int(accu); - - normaccu = _mm_add_pi32(normaccu, _mm_srli_si64(normaccu, 32)); - norm = _m_to_int(normaccu); - - // Clear MMS state - _m_empty(); - - // Normalize result by dividing by sqrt(norm) - this step is easiest - // done using floating point operation - if (norm == 0) norm = 1; // to avoid div by zero - - return (double)corr / sqrt((double)norm); - // Note: Warning about the missing EMMS instruction is harmless - // as it'll be called elsewhere. -} - - - -void TDStretchMMX::clearCrossCorrState() -{ - // Clear MMS state - _m_empty(); - //_asm EMMS; -} - - - -// MMX-optimized version of the function overlapStereo -void TDStretchMMX::overlapStereo(short *output, const short *input) const -{ - const __m64 *pVinput, *pVMidBuf; - __m64 *pVdest; - __m64 mix1, mix2, adder, shifter; - int i; - - pVinput = (const __m64*)input; - pVMidBuf = (const __m64*)pMidBuffer; - pVdest = (__m64*)output; - - // mix1 = mixer values for 1st stereo sample - // mix1 = mixer values for 2nd stereo sample - // adder = adder for updating mixer values after each round - - mix1 = _mm_set_pi16(0, overlapLength, 0, overlapLength); - adder = _mm_set_pi16(1, -1, 1, -1); - mix2 = _mm_add_pi16(mix1, adder); - adder = _mm_add_pi16(adder, adder); - - // Overlaplength-division by shifter. "+1" is to account for "-1" deduced in - // overlapDividerBits calculation earlier. - shifter = _m_from_int(overlapDividerBits + 1); - - for (i = 0; i < overlapLength / 4; i ++) - { - __m64 temp1, temp2; - - // load & shuffle data so that input & mixbuffer data samples are paired - temp1 = _mm_unpacklo_pi16(pVMidBuf[0], pVinput[0]); // = i0l m0l i0r m0r - temp2 = _mm_unpackhi_pi16(pVMidBuf[0], pVinput[0]); // = i1l m1l i1r m1r - - // temp = (temp .* mix) >> shifter - temp1 = _mm_sra_pi32(_mm_madd_pi16(temp1, mix1), shifter); - temp2 = _mm_sra_pi32(_mm_madd_pi16(temp2, mix2), shifter); - pVdest[0] = _mm_packs_pi32(temp1, temp2); // pack 2*2*32bit => 4*16bit - - // update mix += adder - mix1 = _mm_add_pi16(mix1, adder); - mix2 = _mm_add_pi16(mix2, adder); - - // --- second round begins here --- - - // load & shuffle data so that input & mixbuffer data samples are paired - temp1 = _mm_unpacklo_pi16(pVMidBuf[1], pVinput[1]); // = i2l m2l i2r m2r - temp2 = _mm_unpackhi_pi16(pVMidBuf[1], pVinput[1]); // = i3l m3l i3r m3r - - // temp = (temp .* mix) >> shifter - temp1 = _mm_sra_pi32(_mm_madd_pi16(temp1, mix1), shifter); - temp2 = _mm_sra_pi32(_mm_madd_pi16(temp2, mix2), shifter); - pVdest[1] = _mm_packs_pi32(temp1, temp2); // pack 2*2*32bit => 4*16bit - - // update mix += adder - mix1 = _mm_add_pi16(mix1, adder); - mix2 = _mm_add_pi16(mix2, adder); - - pVinput += 2; - pVMidBuf += 2; - pVdest += 2; - } - - _m_empty(); // clear MMS state -} - - -////////////////////////////////////////////////////////////////////////////// -// -// implementation of MMX optimized functions of class 'FIRFilter' -// -////////////////////////////////////////////////////////////////////////////// - -#include "FIRFilter.h" - - -FIRFilterMMX::FIRFilterMMX() : FIRFilter() -{ - filterCoeffsUnalign = NULL; -} - - -FIRFilterMMX::~FIRFilterMMX() -{ - delete[] filterCoeffsUnalign; -} - - -// (overloaded) Calculates filter coefficients for MMX routine -void FIRFilterMMX::setCoefficients(const short *coeffs, uint newLength, uint uResultDivFactor) -{ - uint i; - FIRFilter::setCoefficients(coeffs, newLength, uResultDivFactor); - - // Ensure that filter coeffs array is aligned to 16-byte boundary - delete[] filterCoeffsUnalign; - filterCoeffsUnalign = new short[2 * newLength + 8]; - filterCoeffsAlign = (short *)(((ulong)filterCoeffsUnalign + 15) & -16); - - // rearrange the filter coefficients for mmx routines - for (i = 0;i < length; i += 4) - { - filterCoeffsAlign[2 * i + 0] = coeffs[i + 0]; - filterCoeffsAlign[2 * i + 1] = coeffs[i + 2]; - filterCoeffsAlign[2 * i + 2] = coeffs[i + 0]; - filterCoeffsAlign[2 * i + 3] = coeffs[i + 2]; - - filterCoeffsAlign[2 * i + 4] = coeffs[i + 1]; - filterCoeffsAlign[2 * i + 5] = coeffs[i + 3]; - filterCoeffsAlign[2 * i + 6] = coeffs[i + 1]; - filterCoeffsAlign[2 * i + 7] = coeffs[i + 3]; - } -} - - - -// mmx-optimized version of the filter routine for stereo sound -uint FIRFilterMMX::evaluateFilterStereo(short *dest, const short *src, uint numSamples) const -{ - // Create stack copies of the needed member variables for asm routines : - uint i, j; - __m64 *pVdest = (__m64*)dest; - - if (length < 2) return 0; - - for (i = 0; i < (numSamples - length) / 2; i ++) - { - __m64 accu1; - __m64 accu2; - const __m64 *pVsrc = (const __m64*)src; - const __m64 *pVfilter = (const __m64*)filterCoeffsAlign; - - accu1 = accu2 = _mm_setzero_si64(); - for (j = 0; j < lengthDiv8 * 2; j ++) - { - __m64 temp1, temp2; - - temp1 = _mm_unpacklo_pi16(pVsrc[0], pVsrc[1]); // = l2 l0 r2 r0 - temp2 = _mm_unpackhi_pi16(pVsrc[0], pVsrc[1]); // = l3 l1 r3 r1 - - accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp1, pVfilter[0])); // += l2*f2+l0*f0 r2*f2+r0*f0 - accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp2, pVfilter[1])); // += l3*f3+l1*f1 r3*f3+r1*f1 - - temp1 = _mm_unpacklo_pi16(pVsrc[1], pVsrc[2]); // = l4 l2 r4 r2 - - accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp2, pVfilter[0])); // += l3*f2+l1*f0 r3*f2+r1*f0 - accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp1, pVfilter[1])); // += l4*f3+l2*f1 r4*f3+r2*f1 - - // accu1 += l2*f2+l0*f0 r2*f2+r0*f0 - // += l3*f3+l1*f1 r3*f3+r1*f1 - - // accu2 += l3*f2+l1*f0 r3*f2+r1*f0 - // l4*f3+l2*f1 r4*f3+r2*f1 - - pVfilter += 2; - pVsrc += 2; - } - // accu >>= resultDivFactor - accu1 = _mm_srai_pi32(accu1, resultDivFactor); - accu2 = _mm_srai_pi32(accu2, resultDivFactor); - - // pack 2*2*32bits => 4*16 bits - pVdest[0] = _mm_packs_pi32(accu1, accu2); - src += 4; - pVdest ++; - } - - _m_empty(); // clear emms state - - return (numSamples & 0xfffffffe) - length; -} - -#endif // SOUNDTOUCH_ALLOW_MMX diff --git a/media/libsoundtouch/src/soundtouch.rc b/media/libsoundtouch/src/soundtouch.rc deleted file mode 100644 index 313039b14bf..00000000000 --- a/media/libsoundtouch/src/soundtouch.rc +++ /dev/null @@ -1,53 +0,0 @@ - -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include - -// Note: if you contain versioning information in an included -// RC script, it will be discarded -// Use module.ver to explicitly set these values - -// Do not edit this file. Changes won't affect the build. - - - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// -VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,7,0,0 - PRODUCTVERSION 1,7,0,0 - FILEFLAGSMASK 0x17L -#ifdef _DEBUG - FILEFLAGS 0x1L -#else - FILEFLAGS 0x0L -#endif - FILEOS 0x4L - FILETYPE 0x2L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "000004b0" - BEGIN - VALUE "Comments", "SoundTouch Library licensed for 3rd party applications subject to LGPL license v2.1. Visit http://www.surina.net/soundtouch for more information about the SoundTouch library." - VALUE "FileDescription", "SoundTouch Dynamic Link Library" - VALUE "FileVersion", "1, 7, 0, 0" - VALUE "InternalName", "SoundTouch" - VALUE "LegalCopyright", "Copyright (C) Olli Parviainen 1999-2012" - VALUE "OriginalFilename", "SoundTouch.dll" - VALUE "ProductName", " SoundTouch Dynamic Link Library" - VALUE "ProductVersion", "1, 7, 0, 0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0, 1200 - END -END - diff --git a/media/libsoundtouch/src/soundtouch_config.h b/media/libsoundtouch/src/soundtouch_config.h deleted file mode 100644 index 6807a1a21dc..00000000000 --- a/media/libsoundtouch/src/soundtouch_config.h +++ /dev/null @@ -1,19 +0,0 @@ -#include "mozilla/SSE.h" - -#ifdef MOZ_SAMPLE_TYPE_FLOAT32 -#define SOUNDTOUCH_FLOAT_SAMPLES 1 -#else -#define SOUNDTOUCH_INTEGER_SAMPLES 1 -#endif - -#ifndef MOZILLA_PRESUME_SSE -#ifdef MOZ_SAMPLE_TYPE_FLOAT32 -#define SOUNDTOUCH_DISABLE_X86_OPTIMIZATIONS 1 -#endif -#endif - -#ifndef MOZILLA_PRESUME_MMX -#ifdef MOZ_SAMPLE_TYPE_S16LE -#define SOUNDTOUCH_DISABLE_X86_OPTIMIZATIONS 1 -#endif -#endif diff --git a/media/libsoundtouch/src/sse_optimized.cpp b/media/libsoundtouch/src/sse_optimized.cpp deleted file mode 100644 index ea0d9b6b0a4..00000000000 --- a/media/libsoundtouch/src/sse_optimized.cpp +++ /dev/null @@ -1,361 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// SSE optimized routines for Pentium-III, Athlon-XP and later CPUs. All SSE -/// optimized functions have been gathered into this single source -/// code file, regardless to their class or original source code file, in order -/// to ease porting the library to other compiler and processor platforms. -/// -/// The SSE-optimizations are programmed using SSE compiler intrinsics that -/// are supported both by Microsoft Visual C++ and GCC compilers, so this file -/// should compile with both toolsets. -/// -/// NOTICE: If using Visual Studio 6.0, you'll need to install the "Visual C++ -/// 6.0 processor pack" update to support SSE instruction set. The update is -/// available for download at Microsoft Developers Network, see here: -/// http://msdn.microsoft.com/en-us/vstudio/aa718349.aspx -/// -/// If the above URL is expired or removed, go to "http://msdn.microsoft.com" and -/// perform a search with keywords "processor pack". -/// -/// Author : Copyright (c) Olli Parviainen -/// Author e-mail : oparviai 'at' iki.fi -/// SoundTouch WWW: http://www.surina.net/soundtouch -/// -//////////////////////////////////////////////////////////////////////////////// -// -// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ -// File revision : $Revision: 4 $ -// -// $Id: sse_optimized.cpp 137 2012-04-01 19:49:30Z oparviai $ -// -//////////////////////////////////////////////////////////////////////////////// -// -// License : -// -// SoundTouch audio processing library -// Copyright (c) Olli Parviainen -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -//////////////////////////////////////////////////////////////////////////////// - -#include "cpu_detect.h" -#include "STTypes.h" - -using namespace soundtouch; - -#ifdef SOUNDTOUCH_ALLOW_SSE - -// SSE routines available only with float sample type - -////////////////////////////////////////////////////////////////////////////// -// -// implementation of SSE optimized functions of class 'TDStretchSSE' -// -////////////////////////////////////////////////////////////////////////////// - -#include "TDStretch.h" -#include -#include - -// Calculates cross correlation of two buffers -double TDStretchSSE::calcCrossCorr(const float *pV1, const float *pV2) const -{ - int i; - const float *pVec1; - const __m128 *pVec2; - __m128 vSum, vNorm; - - // Note. It means a major slow-down if the routine needs to tolerate - // unaligned __m128 memory accesses. It's way faster if we can skip - // unaligned slots and use _mm_load_ps instruction instead of _mm_loadu_ps. - // This can mean up to ~ 10-fold difference (incl. part of which is - // due to skipping every second round for stereo sound though). - // - // Compile-time define SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION is provided - // for choosing if this little cheating is allowed. - -#ifdef SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION - // Little cheating allowed, return valid correlation only for - // aligned locations, meaning every second round for stereo sound. - - #define _MM_LOAD _mm_load_ps - - if (((ulong)pV1) & 15) return -1e50; // skip unaligned locations - -#else - // No cheating allowed, use unaligned load & take the resulting - // performance hit. - #define _MM_LOAD _mm_loadu_ps -#endif - - // ensure overlapLength is divisible by 8 - assert((overlapLength % 8) == 0); - - // Calculates the cross-correlation value between 'pV1' and 'pV2' vectors - // Note: pV2 _must_ be aligned to 16-bit boundary, pV1 need not. - pVec1 = (const float*)pV1; - pVec2 = (const __m128*)pV2; - vSum = vNorm = _mm_setzero_ps(); - - // Unroll the loop by factor of 4 * 4 operations. Use same routine for - // stereo & mono, for mono it just means twice the amount of unrolling. - for (i = 0; i < channels * overlapLength / 16; i ++) - { - __m128 vTemp; - // vSum += pV1[0..3] * pV2[0..3] - vTemp = _MM_LOAD(pVec1); - vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp ,pVec2[0])); - vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); - - // vSum += pV1[4..7] * pV2[4..7] - vTemp = _MM_LOAD(pVec1 + 4); - vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[1])); - vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); - - // vSum += pV1[8..11] * pV2[8..11] - vTemp = _MM_LOAD(pVec1 + 8); - vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[2])); - vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); - - // vSum += pV1[12..15] * pV2[12..15] - vTemp = _MM_LOAD(pVec1 + 12); - vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[3])); - vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); - - pVec1 += 16; - pVec2 += 4; - } - - // return value = vSum[0] + vSum[1] + vSum[2] + vSum[3] - float *pvNorm = (float*)&vNorm; - double norm = sqrt(pvNorm[0] + pvNorm[1] + pvNorm[2] + pvNorm[3]); - if (norm < 1e-9) norm = 1.0; // to avoid div by zero - - float *pvSum = (float*)&vSum; - return (double)(pvSum[0] + pvSum[1] + pvSum[2] + pvSum[3]) / norm; - - /* This is approximately corresponding routine in C-language yet without normalization: - double corr, norm; - uint i; - - // Calculates the cross-correlation value between 'pV1' and 'pV2' vectors - corr = norm = 0.0; - for (i = 0; i < channels * overlapLength / 16; i ++) - { - corr += pV1[0] * pV2[0] + - pV1[1] * pV2[1] + - pV1[2] * pV2[2] + - pV1[3] * pV2[3] + - pV1[4] * pV2[4] + - pV1[5] * pV2[5] + - pV1[6] * pV2[6] + - pV1[7] * pV2[7] + - pV1[8] * pV2[8] + - pV1[9] * pV2[9] + - pV1[10] * pV2[10] + - pV1[11] * pV2[11] + - pV1[12] * pV2[12] + - pV1[13] * pV2[13] + - pV1[14] * pV2[14] + - pV1[15] * pV2[15]; - - for (j = 0; j < 15; j ++) norm += pV1[j] * pV1[j]; - - pV1 += 16; - pV2 += 16; - } - return corr / sqrt(norm); - */ -} - - -////////////////////////////////////////////////////////////////////////////// -// -// implementation of SSE optimized functions of class 'FIRFilter' -// -////////////////////////////////////////////////////////////////////////////// - -#include "FIRFilter.h" - -FIRFilterSSE::FIRFilterSSE() : FIRFilter() -{ - filterCoeffsAlign = NULL; - filterCoeffsUnalign = NULL; -} - - -FIRFilterSSE::~FIRFilterSSE() -{ - delete[] filterCoeffsUnalign; - filterCoeffsAlign = NULL; - filterCoeffsUnalign = NULL; -} - - -// (overloaded) Calculates filter coefficients for SSE routine -void FIRFilterSSE::setCoefficients(const float *coeffs, uint newLength, uint uResultDivFactor) -{ - uint i; - float fDivider; - - FIRFilter::setCoefficients(coeffs, newLength, uResultDivFactor); - - // Scale the filter coefficients so that it won't be necessary to scale the filtering result - // also rearrange coefficients suitably for SSE - // Ensure that filter coeffs array is aligned to 16-byte boundary - delete[] filterCoeffsUnalign; - filterCoeffsUnalign = new float[2 * newLength + 4]; - filterCoeffsAlign = (float *)(((unsigned long)filterCoeffsUnalign + 15) & (ulong)-16); - - fDivider = (float)resultDivider; - - // rearrange the filter coefficients for mmx routines - for (i = 0; i < newLength; i ++) - { - filterCoeffsAlign[2 * i + 0] = - filterCoeffsAlign[2 * i + 1] = coeffs[i + 0] / fDivider; - } -} - - - -// SSE-optimized version of the filter routine for stereo sound -uint FIRFilterSSE::evaluateFilterStereo(float *dest, const float *source, uint numSamples) const -{ - int count = (int)((numSamples - length) & (uint)-2); - int j; - - assert(count % 2 == 0); - - if (count < 2) return 0; - - assert(source != NULL); - assert(dest != NULL); - assert((length % 8) == 0); - assert(filterCoeffsAlign != NULL); - assert(((ulong)filterCoeffsAlign) % 16 == 0); - - // filter is evaluated for two stereo samples with each iteration, thus use of 'j += 2' - for (j = 0; j < count; j += 2) - { - const float *pSrc; - const __m128 *pFil; - __m128 sum1, sum2; - uint i; - - pSrc = (const float*)source; // source audio data - pFil = (const __m128*)filterCoeffsAlign; // filter coefficients. NOTE: Assumes coefficients - // are aligned to 16-byte boundary - sum1 = sum2 = _mm_setzero_ps(); - - for (i = 0; i < length / 8; i ++) - { - // Unroll loop for efficiency & calculate filter for 2*2 stereo samples - // at each pass - - // sum1 is accu for 2*2 filtered stereo sound data at the primary sound data offset - // sum2 is accu for 2*2 filtered stereo sound data for the next sound sample offset. - - sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc) , pFil[0])); - sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 2), pFil[0])); - - sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 4), pFil[1])); - sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 6), pFil[1])); - - sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 8) , pFil[2])); - sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 10), pFil[2])); - - sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 12), pFil[3])); - sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 14), pFil[3])); - - pSrc += 16; - pFil += 4; - } - - // Now sum1 and sum2 both have a filtered 2-channel sample each, but we still need - // to sum the two hi- and lo-floats of these registers together. - - // post-shuffle & add the filtered values and store to dest. - _mm_storeu_ps(dest, _mm_add_ps( - _mm_shuffle_ps(sum1, sum2, _MM_SHUFFLE(1,0,3,2)), // s2_1 s2_0 s1_3 s1_2 - _mm_shuffle_ps(sum1, sum2, _MM_SHUFFLE(3,2,1,0)) // s2_3 s2_2 s1_1 s1_0 - )); - source += 4; - dest += 4; - } - - // Ideas for further improvement: - // 1. If it could be guaranteed that 'source' were always aligned to 16-byte - // boundary, a faster aligned '_mm_load_ps' instruction could be used. - // 2. If it could be guaranteed that 'dest' were always aligned to 16-byte - // boundary, a faster '_mm_store_ps' instruction could be used. - - return (uint)count; - - /* original routine in C-language. please notice the C-version has differently - organized coefficients though. - double suml1, suml2; - double sumr1, sumr2; - uint i, j; - - for (j = 0; j < count; j += 2) - { - const float *ptr; - const float *pFil; - - suml1 = sumr1 = 0.0; - suml2 = sumr2 = 0.0; - ptr = src; - pFil = filterCoeffs; - for (i = 0; i < lengthLocal; i ++) - { - // unroll loop for efficiency. - - suml1 += ptr[0] * pFil[0] + - ptr[2] * pFil[2] + - ptr[4] * pFil[4] + - ptr[6] * pFil[6]; - - sumr1 += ptr[1] * pFil[1] + - ptr[3] * pFil[3] + - ptr[5] * pFil[5] + - ptr[7] * pFil[7]; - - suml2 += ptr[8] * pFil[0] + - ptr[10] * pFil[2] + - ptr[12] * pFil[4] + - ptr[14] * pFil[6]; - - sumr2 += ptr[9] * pFil[1] + - ptr[11] * pFil[3] + - ptr[13] * pFil[5] + - ptr[15] * pFil[7]; - - ptr += 16; - pFil += 8; - } - dest[0] = (float)suml1; - dest[1] = (float)sumr1; - dest[2] = (float)suml2; - dest[3] = (float)sumr2; - - src += 4; - dest += 4; - } - */ -} - -#endif // SOUNDTOUCH_ALLOW_SSE diff --git a/media/libsoundtouch/update.sh b/media/libsoundtouch/update.sh deleted file mode 100644 index 9940601bcd0..00000000000 --- a/media/libsoundtouch/update.sh +++ /dev/null @@ -1,40 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Usage: ./update.sh -# -# Copies the needed files from a directory containing the original -# soundtouch sources that we need for HTML5 media playback rate change. - -cp $1/COPYING.TXT . -cp $1/source/SoundTouch/AAFilter.cpp src -cp $1/source/SoundTouch/AAFilter.h src -cp $1/source/SoundTouch/cpu_detect.h src -cp $1/source/SoundTouch/cpu_detect_x86.cpp src -cp $1/source/SoundTouch/FIFOSampleBuffer.cpp src -cp $1/source/SoundTouch/FIRFilter.cpp src -cp $1/source/SoundTouch/FIRFilter.h src -cp $1/source/SoundTouch/mmx_optimized.cpp src -cp $1/source/SoundTouch/RateTransposer.cpp src -cp $1/source/SoundTouch/RateTransposer.h src -cp $1/source/SoundTouch/SoundTouch.cpp src -cp $1/source/SoundTouch/sse_optimized.cpp src -cp $1/source/SoundTouch/TDStretch.cpp src -cp $1/source/SoundTouch/TDStretch.h src -cp $1/include/SoundTouch.h src -cp $1/include/FIFOSampleBuffer.h src -cp $1/include/FIFOSamplePipe.h src -cp $1/include/SoundTouch.h src -cp $1/include/STTypes.h src - -# Remote the Windows line ending characters from the files. -for i in src/* -do - cat $i | tr -d '\015' > $i.lf - mv $i.lf $i -done - -# Patch the imported files. -patch -p1 < moz-libsoundtouch.patch - diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 9d7ed11b58d..7253073b50e 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -40,7 +40,6 @@ @BINPATH@/@DLL_PREFIX@xpcom@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ -@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@omxplugin@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@xul@DLL_SUFFIX@ diff --git a/mobile/xul/installer/package-manifest.in b/mobile/xul/installer/package-manifest.in index 94ea7af4bec..8b0fd4892d8 100644 --- a/mobile/xul/installer/package-manifest.in +++ b/mobile/xul/installer/package-manifest.in @@ -51,7 +51,6 @@ @BINPATH@/@DLL_PREFIX@xpcom@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ -@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@omxplugin@DLL_SUFFIX@ #ifdef XP_MACOSX diff --git a/toolkit/content/license.html b/toolkit/content/license.html index e1888afb612..b2b4a1b881b 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -87,7 +87,6 @@

  • libffi License
  • libjingle License
  • libnestegg License
  • -
  • libsoundtouch License
  • libsrtp License
  • libunwind License
  • libyuv License
  • @@ -1968,32 +1967,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    -

    libsoundtouch License

    - -

    This license applies to certain files in the directory - media/libsoundtouch/src/. -

    - -
    -The SoundTouch Library Copyright © Olli Parviainen 2001-2012
    -
    -This library is free software; you can redistribute it and/or
    -modify it under the terms of the GNU Lesser General Public
    -License as published by the Free Software Foundation; either
    -version 2.1 of the License, or (at your option) any later version.
    -
    -This library is distributed in the hope that it will be useful,
    -but WITHOUT ANY WARRANTY; without even the implied warranty of
    -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    -Lesser General Public License for more details.
    -
    -You should have received a copy of the GNU Lesser General Public
    -License along with this library; if not, write to the Free Software
    -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
    -
    - -
    -

    libsrtp License

    This license applies to files in the directory diff --git a/toolkit/library/Makefile.in b/toolkit/library/Makefile.in index 677f4540806..8ddeb2cb1f6 100644 --- a/toolkit/library/Makefile.in +++ b/toolkit/library/Makefile.in @@ -358,7 +358,6 @@ EXTRA_DSO_LDOPTS += \ $(MOZ_CAIRO_OSLIBS) \ $(MOZ_APP_EXTRA_LIBS) \ $(SQLITE_LIBS) \ - $(SOUNDTOUCH_LIBS) \ $(NULL) ifdef MOZ_NATIVE_JPEG diff --git a/toolkit/mozapps/installer/packager.mk b/toolkit/mozapps/installer/packager.mk index dd63f6de6b9..6da907a07ef 100644 --- a/toolkit/mozapps/installer/packager.mk +++ b/toolkit/mozapps/installer/packager.mk @@ -262,7 +262,6 @@ DIST_FILES += \ libplc4.so \ libplds4.so \ libmozsqlite3.so \ - libsoundtouch.so \ libnssutil3.so \ libnss3.so \ libssl3.so \ diff --git a/toolkit/toolkit-makefiles.sh b/toolkit/toolkit-makefiles.sh index 5ae3a99c9d9..91d25475aee 100644 --- a/toolkit/toolkit-makefiles.sh +++ b/toolkit/toolkit-makefiles.sh @@ -1737,11 +1737,3 @@ if [ "$MOZ_SPEEX_RESAMPLER" ]; then media/libspeex_resampler/src/Makefile " fi - -if [ "$MOZ_SOUNDTOUCH" ]; then - add_makefiles " - media/libsoundtouch/Makefile - media/libsoundtouch/src/Makefile - " -fi - diff --git a/toolkit/toolkit-tiers.mk b/toolkit/toolkit-tiers.mk index aecd4f56e69..4b16d2380c1 100644 --- a/toolkit/toolkit-tiers.mk +++ b/toolkit/toolkit-tiers.mk @@ -151,12 +151,6 @@ tier_platform_dirs += \ $(NULL) endif -ifdef MOZ_SOUNDTOUCH -tier_platform_dirs += \ - media/libsoundtouch \ - $(NULL) -endif - ifdef MOZ_CUBEB tier_platform_dirs += \ media/libcubeb \ From 061130052b6c4d00d3f8204d6695156f61036b5e Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Sun, 26 Aug 2012 22:36:44 -0700 Subject: [PATCH 10/77] Bug 779997 - Import SoundTouch Library in the tree. r=khuey,rstrong --- b2g/installer/package-manifest.in | 1 + browser/installer/package-manifest.in | 1 + config/system-headers | 3 +- configure.in | 15 + js/src/config/system-headers | 3 +- media/libsoundtouch/AUTHORS | 4 + media/libsoundtouch/LICENSE | 458 +++++++++++ media/libsoundtouch/Makefile.in | 17 + media/libsoundtouch/README_MOZILLA | 8 + media/libsoundtouch/moz-libsoundtouch.patch | 509 ++++++++++++ media/libsoundtouch/src/AAFilter.cpp | 184 +++++ media/libsoundtouch/src/AAFilter.h | 91 +++ media/libsoundtouch/src/FIFOSampleBuffer.cpp | 274 +++++++ media/libsoundtouch/src/FIFOSampleBuffer.h | 178 ++++ media/libsoundtouch/src/FIFOSamplePipe.h | 234 ++++++ media/libsoundtouch/src/FIRFilter.cpp | 259 ++++++ media/libsoundtouch/src/FIRFilter.h | 145 ++++ media/libsoundtouch/src/Makefile.in | 69 ++ media/libsoundtouch/src/RateTransposer.cpp | 626 ++++++++++++++ media/libsoundtouch/src/RateTransposer.h | 159 ++++ media/libsoundtouch/src/STTypes.h | 159 ++++ media/libsoundtouch/src/SoundTouch.cpp | 501 ++++++++++++ media/libsoundtouch/src/SoundTouch.h | 277 +++++++ media/libsoundtouch/src/TDStretch.cpp | 808 +++++++++++++++++++ media/libsoundtouch/src/TDStretch.h | 268 ++++++ media/libsoundtouch/src/cpu_detect.h | 62 ++ media/libsoundtouch/src/cpu_detect_x86.cpp | 144 ++++ media/libsoundtouch/src/mmx_optimized.cpp | 317 ++++++++ media/libsoundtouch/src/soundtouch.rc | 53 ++ media/libsoundtouch/src/soundtouch_config.h | 19 + media/libsoundtouch/src/sse_optimized.cpp | 361 +++++++++ media/libsoundtouch/update.sh | 40 + mobile/android/installer/package-manifest.in | 1 + mobile/xul/installer/package-manifest.in | 1 + toolkit/content/license.html | 27 + toolkit/library/Makefile.in | 1 + toolkit/mozapps/installer/packager.mk | 1 + toolkit/toolkit-makefiles.sh | 8 + toolkit/toolkit-tiers.mk | 6 + 39 files changed, 6290 insertions(+), 2 deletions(-) create mode 100644 media/libsoundtouch/AUTHORS create mode 100644 media/libsoundtouch/LICENSE create mode 100644 media/libsoundtouch/Makefile.in create mode 100644 media/libsoundtouch/README_MOZILLA create mode 100644 media/libsoundtouch/moz-libsoundtouch.patch create mode 100644 media/libsoundtouch/src/AAFilter.cpp create mode 100644 media/libsoundtouch/src/AAFilter.h create mode 100644 media/libsoundtouch/src/FIFOSampleBuffer.cpp create mode 100644 media/libsoundtouch/src/FIFOSampleBuffer.h create mode 100644 media/libsoundtouch/src/FIFOSamplePipe.h create mode 100644 media/libsoundtouch/src/FIRFilter.cpp create mode 100644 media/libsoundtouch/src/FIRFilter.h create mode 100644 media/libsoundtouch/src/Makefile.in create mode 100644 media/libsoundtouch/src/RateTransposer.cpp create mode 100644 media/libsoundtouch/src/RateTransposer.h create mode 100644 media/libsoundtouch/src/STTypes.h create mode 100644 media/libsoundtouch/src/SoundTouch.cpp create mode 100644 media/libsoundtouch/src/SoundTouch.h create mode 100644 media/libsoundtouch/src/TDStretch.cpp create mode 100644 media/libsoundtouch/src/TDStretch.h create mode 100644 media/libsoundtouch/src/cpu_detect.h create mode 100644 media/libsoundtouch/src/cpu_detect_x86.cpp create mode 100644 media/libsoundtouch/src/mmx_optimized.cpp create mode 100644 media/libsoundtouch/src/soundtouch.rc create mode 100644 media/libsoundtouch/src/soundtouch_config.h create mode 100644 media/libsoundtouch/src/sse_optimized.cpp create mode 100644 media/libsoundtouch/update.sh diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 9399b762c81..8687a1eed2b 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -54,6 +54,7 @@ @BINPATH@/@DLL_PREFIX@xpcom@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ #ifdef XP_MACOSX @BINPATH@/XUL #else diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 52f3f4be673..266b49dba0c 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -54,6 +54,7 @@ @BINPATH@/@DLL_PREFIX@gkmedias@DLL_SUFFIX@ #endif @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ #ifdef MOZ_SHARED_MOZGLUE @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@ #endif diff --git a/config/system-headers b/config/system-headers index f781c8220d0..74fc247fdb0 100644 --- a/config/system-headers +++ b/config/system-headers @@ -981,6 +981,8 @@ plstr.h plarenas.h plarena.h plhash.h +speex/speex_resampler.h +soundtouch/SoundTouch.h #if MOZ_NATIVE_PNG==1 png.h #endif @@ -1053,7 +1055,6 @@ vpx/vpx_encoder.h vpx/vp8cx.h vpx/vp8dx.h sydneyaudio/sydney_audio.h -speex/speex_resampler.h vorbis/codec.h theora/theoradec.h tremor/ivorbiscodec.h diff --git a/configure.in b/configure.in index ef2acbc7a85..5bb372e5a02 100644 --- a/configure.in +++ b/configure.in @@ -4225,6 +4225,7 @@ MOZ_OGG=1 MOZ_RAW= MOZ_SYDNEYAUDIO= MOZ_SPEEX_RESAMPLER=1 +MOZ_SOUNDTOUCH=1 MOZ_CUBEB= MOZ_VORBIS= MOZ_TREMOR= @@ -5580,6 +5581,19 @@ if test -n "$MOZ_SPEEX_RESAMPLER"; then AC_DEFINE(MOZ_SPEEX_RESAMPLER) fi +if test -n "$MOZ_SOUNDTOUCH"; then + AC_DEFINE(MOZ_SOUNDTOUCH) +fi + +if test -z "$GNU_CC" -a "$OS_ARCH" = "WINNT"; then + SOUNDTOUCH_LIBS='$(LIBXUL_DIST)/lib/$(LIB_PREFIX)soundtouch.$(LIB_SUFFIX)' +else + SOUNDTOUCH_LIBS='-lsoundtouch' +fi +AC_SUBST(SOUNDTOUCH_CFLAGS) +AC_SUBST(SOUNDTOUCH_LIBS) +AC_SUBST(SOUNDTOUCH_CONFIG) + if test -n "$MOZ_CUBEB"; then case "$target" in *-android*|*-linuxandroid*) @@ -8653,6 +8667,7 @@ AC_SUBST(MOZ_APP_EXTRA_LIBS) AC_SUBST(MOZ_MEDIA) AC_SUBST(MOZ_SYDNEYAUDIO) AC_SUBST(MOZ_SPEEX_RESAMPLER) +AC_SUBST(MOZ_SOUNDTOUCH) AC_SUBST(MOZ_CUBEB) AC_SUBST(MOZ_WAVE) AC_SUBST(MOZ_VORBIS) diff --git a/js/src/config/system-headers b/js/src/config/system-headers index f781c8220d0..74fc247fdb0 100644 --- a/js/src/config/system-headers +++ b/js/src/config/system-headers @@ -981,6 +981,8 @@ plstr.h plarenas.h plarena.h plhash.h +speex/speex_resampler.h +soundtouch/SoundTouch.h #if MOZ_NATIVE_PNG==1 png.h #endif @@ -1053,7 +1055,6 @@ vpx/vpx_encoder.h vpx/vp8cx.h vpx/vp8dx.h sydneyaudio/sydney_audio.h -speex/speex_resampler.h vorbis/codec.h theora/theoradec.h tremor/ivorbiscodec.h diff --git a/media/libsoundtouch/AUTHORS b/media/libsoundtouch/AUTHORS new file mode 100644 index 00000000000..666081e1952 --- /dev/null +++ b/media/libsoundtouch/AUTHORS @@ -0,0 +1,4 @@ +The SoundTouch Library +Copyright © Olli Parviainen 2001-2012 + +http://www.surina.net/soundtouch/ diff --git a/media/libsoundtouch/LICENSE b/media/libsoundtouch/LICENSE new file mode 100644 index 00000000000..5b2161be204 --- /dev/null +++ b/media/libsoundtouch/LICENSE @@ -0,0 +1,458 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authoried party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/media/libsoundtouch/Makefile.in b/media/libsoundtouch/Makefile.in new file mode 100644 index 00000000000..a8c74390b16 --- /dev/null +++ b/media/libsoundtouch/Makefile.in @@ -0,0 +1,17 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = soundtouch + +DIRS = src \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/media/libsoundtouch/README_MOZILLA b/media/libsoundtouch/README_MOZILLA new file mode 100644 index 00000000000..141860babd4 --- /dev/null +++ b/media/libsoundtouch/README_MOZILLA @@ -0,0 +1,8 @@ +These files are from the SoundTouch library (http://www.surina.net/soundtouch/), +and are extracted from the revision r143 of the svn repository at +https://soundtouch.svn.sourceforge.net/svnroot/soundtouch/trunk. + +The whole library is not used, only the relevant files are imported in the tree, +using the script `update.sh`. Some changes have been made to the files, using +the patch `moz-libsoundtouch.patch`. We also use a custom soundtouch_config.h. + diff --git a/media/libsoundtouch/moz-libsoundtouch.patch b/media/libsoundtouch/moz-libsoundtouch.patch new file mode 100644 index 00000000000..f78ec818561 --- /dev/null +++ b/media/libsoundtouch/moz-libsoundtouch.patch @@ -0,0 +1,509 @@ +unchanged: +--- /src/STTypes.h 2012-08-02 10:04:06.301691592 -0700 ++++ /src/STTypes.h +@@ -42,21 +42,13 @@ + typedef unsigned int uint; + typedef unsigned long ulong; + +-#ifdef __GNUC__ +- // In GCC, include soundtouch_config.h made by config scritps +- #include "soundtouch_config.h" +-#endif +- +-#ifndef _WINDEF_ +- // if these aren't defined already by Windows headers, define now +- +- typedef int BOOL; +- +- #define FALSE 0 +- #define TRUE 1 +- +-#endif // _WINDEF_ ++#include "soundtouch_config.h" + ++#ifdef WIN32 ++#define EXPORT __declspec(dllexport) ++#else ++#define EXPORT ++#endif + + namespace soundtouch + { +@@ -82,7 +74,7 @@ + /// also in GNU environment, then please #undef the INTEGER_SAMPLE + /// and FLOAT_SAMPLE defines first as in comments above. + //#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples +- #define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples ++ #define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples + + #endif + +@@ -144,10 +136,10 @@ + + #endif // SOUNDTOUCH_INTEGER_SAMPLES + +-}; ++} + + // define ST_NO_EXCEPTION_HANDLING switch to disable throwing std exceptions: +-// #define ST_NO_EXCEPTION_HANDLING 1 ++#define ST_NO_EXCEPTION_HANDLING 1 + #ifdef ST_NO_EXCEPTION_HANDLING + // Exceptions disabled. Throw asserts instead if enabled. + #include +--- /src/SoundTouch.h 2012-08-02 10:04:06.301691592 -0700 ++++ /src/SoundTouch.h +@@ -141,7 +141,7 @@ + /// tempo/pitch/rate/samplerate settings. + #define SETTING_NOMINAL_OUTPUT_SEQUENCE 7 + +-class SoundTouch : public FIFOProcessor ++class EXPORT SoundTouch : public FIFOProcessor + { + private: + /// Rate transposer class instance +@@ -160,7 +160,7 @@ + float virtualPitch; + + /// Flag: Has sample rate been set? +- BOOL bSrateSet; ++ bool bSrateSet; + + /// Calculates effective rate & tempo valuescfrom 'virtualRate', 'virtualTempo' and + /// 'virtualPitch' parameters. +@@ -247,8 +247,8 @@ + /// Changes a setting controlling the processing system behaviour. See the + /// 'SETTING_...' defines for available setting ID's. + /// +- /// \return 'TRUE' if the setting was succesfully changed +- BOOL setSetting(int settingId, ///< Setting ID number. see SETTING_... defines. ++ /// \return 'true' if the setting was succesfully changed ++ bool setSetting(int settingId, ///< Setting ID number. see SETTING_... defines. + int value ///< New setting value. + ); + +--- /src/RateTransposer.cpp ++++ /src/RateTransposer.cpp +@@ -120,17 +120,17 @@ RateTransposer *RateTransposer::newInsta + #endif + } + + + // Constructor + RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer) + { + numChannels = 2; +- bUseAAFilter = TRUE; ++ bUseAAFilter = true; + fRate = 0; + + // Instantiates the anti-alias filter with default tap length + // of 32 + pAAFilter = new AAFilter(32); + } + + +@@ -138,24 +138,24 @@ RateTransposer::RateTransposer() : FIFOP + RateTransposer::~RateTransposer() + { + delete pAAFilter; + } + + + + /// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable +-void RateTransposer::enableAAFilter(BOOL newMode) ++void RateTransposer::enableAAFilter(bool newMode) + { + bUseAAFilter = newMode; + } + + + /// Returns nonzero if anti-alias filter is enabled. +-BOOL RateTransposer::isAAFilterEnabled() const ++bool RateTransposer::isAAFilterEnabled() const + { + return bUseAAFilter; + } + + + AAFilter *RateTransposer::getAAFilter() + { + return pAAFilter; +@@ -281,17 +281,17 @@ void RateTransposer::processSamples(cons + uint count; + uint sizeReq; + + if (nSamples == 0) return; + assert(pAAFilter); + + // If anti-alias filter is turned off, simply transpose without applying + // the filter +- if (bUseAAFilter == FALSE) ++ if (bUseAAFilter == false) + { + sizeReq = (uint)((float)nSamples / fRate + 1.0f); + count = transpose(outputBuffer.ptrEnd(sizeReq), src, nSamples); + outputBuffer.putSamples(count); + return; + } + + // Transpose with anti-alias filter +--- /src/RateTransposer.h ++++ /src/RateTransposer.h +@@ -76,17 +76,17 @@ protected: + FIFOSampleBuffer storeBuffer; + + /// Buffer for keeping samples between transposing & anti-alias filter + FIFOSampleBuffer tempBuffer; + + /// Output sample buffer + FIFOSampleBuffer outputBuffer; + +- BOOL bUseAAFilter; ++ bool bUseAAFilter; + + virtual void resetRegisters() = 0; + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) = 0; + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, +@@ -126,20 +126,20 @@ public: + + /// Returns the store buffer object + FIFOSamplePipe *getStore() { return &storeBuffer; }; + + /// Return anti-alias filter object + AAFilter *getAAFilter(); + + /// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable +- void enableAAFilter(BOOL newMode); ++ void enableAAFilter(bool newMode); + + /// Returns nonzero if anti-alias filter is enabled. +- BOOL isAAFilterEnabled() const; ++ bool isAAFilterEnabled() const; + + /// Sets new target rate. Normal rate = 1.0, smaller values represent slower + /// rate, larger faster rates. + virtual void setRate(float newRate); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(int channels); + +--- /src/SoundTouch.cpp ++++ /src/SoundTouch.cpp +@@ -106,17 +106,17 @@ SoundTouch::SoundTouch() + + virtualPitch = + virtualRate = + virtualTempo = 1.0; + + calcEffectiveRateAndTempo(); + + channels = 0; +- bSrateSet = FALSE; ++ bSrateSet = false; + } + + + + SoundTouch::~SoundTouch() + { + delete pRateTransposer; + delete pTDStretch; +@@ -277,27 +277,27 @@ void SoundTouch::calcEffectiveRateAndTem + } + } + } + + + // Sets sample rate. + void SoundTouch::setSampleRate(uint srate) + { +- bSrateSet = TRUE; ++ bSrateSet = true; + // set sample rate, leave other tempo changer parameters as they are. + pTDStretch->setParameters((int)srate); + } + + + // Adds 'numSamples' pcs of samples from the 'samples' memory position into + // the input of the object. + void SoundTouch::putSamples(const SAMPLETYPE *samples, uint nSamples) + { +- if (bSrateSet == FALSE) ++ if (bSrateSet == false) + { + ST_THROW_RT_ERROR("SoundTouch : Sample rate not defined"); + } + else if (channels == 0) + { + ST_THROW_RT_ERROR("SoundTouch : Number of channels not defined"); + } + +@@ -382,57 +382,57 @@ void SoundTouch::flush() + pTDStretch->clearInput(); + // yet leave the 'tempoChanger' output intouched as that's where the + // flushed samples are! + } + + + // Changes a setting controlling the processing system behaviour. See the + // 'SETTING_...' defines for available setting ID's. +-BOOL SoundTouch::setSetting(int settingId, int value) ++bool SoundTouch::setSetting(int settingId, int value) + { + int sampleRate, sequenceMs, seekWindowMs, overlapMs; + + // read current tdstretch routine parameters + pTDStretch->getParameters(&sampleRate, &sequenceMs, &seekWindowMs, &overlapMs); + + switch (settingId) + { + case SETTING_USE_AA_FILTER : + // enables / disabless anti-alias filter +- pRateTransposer->enableAAFilter((value != 0) ? TRUE : FALSE); +- return TRUE; ++ pRateTransposer->enableAAFilter((value != 0) ? true : false); ++ return true; + + case SETTING_AA_FILTER_LENGTH : + // sets anti-alias filter length + pRateTransposer->getAAFilter()->setLength(value); +- return TRUE; ++ return true; + + case SETTING_USE_QUICKSEEK : + // enables / disables tempo routine quick seeking algorithm +- pTDStretch->enableQuickSeek((value != 0) ? TRUE : FALSE); +- return TRUE; ++ pTDStretch->enableQuickSeek((value != 0) ? true : false); ++ return true; + + case SETTING_SEQUENCE_MS: + // change time-stretch sequence duration parameter + pTDStretch->setParameters(sampleRate, value, seekWindowMs, overlapMs); +- return TRUE; ++ return true; + + case SETTING_SEEKWINDOW_MS: + // change time-stretch seek window length parameter + pTDStretch->setParameters(sampleRate, sequenceMs, value, overlapMs); +- return TRUE; ++ return true; + + case SETTING_OVERLAP_MS: + // change time-stretch overlap length parameter + pTDStretch->setParameters(sampleRate, sequenceMs, seekWindowMs, value); +- return TRUE; ++ return true; + + default : +- return FALSE; ++ return false; + } + } + + + // Reads a setting controlling the processing system behaviour. See the + // 'SETTING_...' defines for available setting ID's. + // + // Returns the setting value. +--- /src/TDStretch.cpp ++++ /src/TDStretch.cpp +@@ -81,25 +81,25 @@ static const short _scanOffsets[5][24]={ + * + * Implementation of the class 'TDStretch' + * + *****************************************************************************/ + + + TDStretch::TDStretch() : FIFOProcessor(&outputBuffer) + { +- bQuickSeek = FALSE; ++ bQuickSeek = false; + channels = 2; + + pMidBuffer = NULL; + pMidBufferUnaligned = NULL; + overlapLength = 0; + +- bAutoSeqSetting = TRUE; +- bAutoSeekSetting = TRUE; ++ bAutoSeqSetting = true; ++ bAutoSeekSetting = true; + + // outDebt = 0; + skipFract = 0; + + tempo = 1.0f; + setParameters(44100, DEFAULT_SEQUENCE_MS, DEFAULT_SEEKWINDOW_MS, DEFAULT_OVERLAP_MS); + setTempo(1.0f); + +@@ -129,33 +129,33 @@ void TDStretch::setParameters(int aSampl + { + // accept only positive parameter values - if zero or negative, use old values instead + if (aSampleRate > 0) this->sampleRate = aSampleRate; + if (aOverlapMS > 0) this->overlapMs = aOverlapMS; + + if (aSequenceMS > 0) + { + this->sequenceMs = aSequenceMS; +- bAutoSeqSetting = FALSE; ++ bAutoSeqSetting = false; + } + else if (aSequenceMS == 0) + { + // if zero, use automatic setting +- bAutoSeqSetting = TRUE; ++ bAutoSeqSetting = true; + } + + if (aSeekWindowMS > 0) + { + this->seekWindowMs = aSeekWindowMS; +- bAutoSeekSetting = FALSE; ++ bAutoSeekSetting = false; + } + else if (aSeekWindowMS == 0) + { + // if zero, use automatic setting +- bAutoSeekSetting = TRUE; ++ bAutoSeekSetting = true; + } + + calcSeqParameters(); + + calculateOverlapLength(overlapMs); + + // set tempo to recalculate 'sampleReq' + setTempo(tempo); +@@ -229,24 +229,24 @@ void TDStretch::clear() + outputBuffer.clear(); + clearInput(); + } + + + + // Enables/disables the quick position seeking algorithm. Zero to disable, nonzero + // to enable +-void TDStretch::enableQuickSeek(BOOL enable) ++void TDStretch::enableQuickSeek(bool enable) + { + bQuickSeek = enable; + } + + + // Returns nonzero if the quick seeking algorithm is enabled. +-BOOL TDStretch::isQuickSeekEnabled() const ++bool TDStretch::isQuickSeekEnabled() const + { + return bQuickSeek; + } + + + // Seeks for the optimal overlap-mixing position. + int TDStretch::seekBestOverlapPosition(const SAMPLETYPE *refPos) + { +--- /src/TDStretch.h ++++ /src/TDStretch.h +@@ -120,24 +120,24 @@ protected: + int seekLength; + int seekWindowLength; + int overlapDividerBits; + int slopingDivider; + float nominalSkip; + float skipFract; + FIFOSampleBuffer outputBuffer; + FIFOSampleBuffer inputBuffer; +- BOOL bQuickSeek; ++ bool bQuickSeek; + + int sampleRate; + int sequenceMs; + int seekWindowMs; + int overlapMs; +- BOOL bAutoSeqSetting; +- BOOL bAutoSeekSetting; ++ bool bAutoSeqSetting; ++ bool bAutoSeekSetting; + + void acceptNewOverlapLength(int newOverlapLength); + + virtual void clearCrossCorrState(); + void calculateOverlapLength(int overlapMs); + + virtual double calcCrossCorr(const SAMPLETYPE *mixingPos, const SAMPLETYPE *compare) const; + +@@ -188,20 +188,20 @@ public: + /// Clears the input buffer + void clearInput(); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(int numChannels); + + /// Enables/disables the quick position seeking algorithm. Zero to disable, + /// nonzero to enable +- void enableQuickSeek(BOOL enable); ++ void enableQuickSeek(bool enable); + + /// Returns nonzero if the quick seeking algorithm is enabled. +- BOOL isQuickSeekEnabled() const; ++ bool isQuickSeekEnabled() const; + + /// Sets routine control parameters. These control are certain time constants + /// defining how the sound is stretched to the desired duration. + // + /// 'sampleRate' = sample rate of the sound + /// 'sequenceMS' = one processing sequence length in milliseconds + /// 'seekwindowMS' = seeking window length for scanning the best overlapping + /// position +only in patch2: +--- /src/cpu_detect_x86.cpp 2012-04-12 19:52:12.743376976 -0700 ++++ /src/cpu_detect_x86.cpp 2012-08-02 09:54:24.561712171 -0700 +@@ -38,30 +38,35 @@ + // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + // + //////////////////////////////////////////////////////////////////////////////// + + #include "cpu_detect.h" + #include "STTypes.h" + + #if defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) +- +- #if defined(__GNUC__) && defined(__i386__) +- // gcc ++ #if defined(__GNUC__) ++ // gcc and clang + #include "cpuid.h" + #endif + + #if defined(_M_IX86) + // windows + #include +- #define bit_MMX (1 << 23) +- #define bit_SSE (1 << 25) +- #define bit_SSE2 (1 << 26) + #endif +- ++ // If we still don't have the macros, define them (Windows, MacOS) ++ #ifndef bit_MMX ++ #define bit_MMX (1 << 23) ++ #endif ++ #ifndef bit_SSE ++ #define bit_SSE (1 << 25) ++ #endif ++ #ifndef bit_SSE2 ++ #define bit_SSE2 (1 << 26) ++ #endif + #endif + + + ////////////////////////////////////////////////////////////////////////////// + // + // processor instructions extension detection routines + // + ////////////////////////////////////////////////////////////////////////////// diff --git a/media/libsoundtouch/src/AAFilter.cpp b/media/libsoundtouch/src/AAFilter.cpp new file mode 100644 index 00000000000..29de4096b4b --- /dev/null +++ b/media/libsoundtouch/src/AAFilter.cpp @@ -0,0 +1,184 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// FIR low-pass (anti-alias) filter with filter coefficient design routine and +/// MMX optimization. +/// +/// Anti-alias filter is used to prevent folding of high frequencies when +/// transposing the sample rate with interpolation. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2009-01-11 03:34:24 -0800 (Sun, 11 Jan 2009) $ +// File revision : $Revision: 4 $ +// +// $Id: AAFilter.cpp 45 2009-01-11 11:34:24Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "AAFilter.h" +#include "FIRFilter.h" + +using namespace soundtouch; + +#define PI 3.141592655357989 +#define TWOPI (2 * PI) + +/***************************************************************************** + * + * Implementation of the class 'AAFilter' + * + *****************************************************************************/ + +AAFilter::AAFilter(uint len) +{ + pFIR = FIRFilter::newInstance(); + cutoffFreq = 0.5; + setLength(len); +} + + + +AAFilter::~AAFilter() +{ + delete pFIR; +} + + + +// Sets new anti-alias filter cut-off edge frequency, scaled to +// sampling frequency (nyquist frequency = 0.5). +// The filter will cut frequencies higher than the given frequency. +void AAFilter::setCutoffFreq(double newCutoffFreq) +{ + cutoffFreq = newCutoffFreq; + calculateCoeffs(); +} + + + +// Sets number of FIR filter taps +void AAFilter::setLength(uint newLength) +{ + length = newLength; + calculateCoeffs(); +} + + + +// Calculates coefficients for a low-pass FIR filter using Hamming window +void AAFilter::calculateCoeffs() +{ + uint i; + double cntTemp, temp, tempCoeff,h, w; + double fc2, wc; + double scaleCoeff, sum; + double *work; + SAMPLETYPE *coeffs; + + assert(length >= 2); + assert(length % 4 == 0); + assert(cutoffFreq >= 0); + assert(cutoffFreq <= 0.5); + + work = new double[length]; + coeffs = new SAMPLETYPE[length]; + + fc2 = 2.0 * cutoffFreq; + wc = PI * fc2; + tempCoeff = TWOPI / (double)length; + + sum = 0; + for (i = 0; i < length; i ++) + { + cntTemp = (double)i - (double)(length / 2); + + temp = cntTemp * wc; + if (temp != 0) + { + h = fc2 * sin(temp) / temp; // sinc function + } + else + { + h = 1.0; + } + w = 0.54 + 0.46 * cos(tempCoeff * cntTemp); // hamming window + + temp = w * h; + work[i] = temp; + + // calc net sum of coefficients + sum += temp; + } + + // ensure the sum of coefficients is larger than zero + assert(sum > 0); + + // ensure we've really designed a lowpass filter... + assert(work[length/2] > 0); + assert(work[length/2 + 1] > -1e-6); + assert(work[length/2 - 1] > -1e-6); + + // Calculate a scaling coefficient in such a way that the result can be + // divided by 16384 + scaleCoeff = 16384.0f / sum; + + for (i = 0; i < length; i ++) + { + // scale & round to nearest integer + temp = work[i] * scaleCoeff; + temp += (temp >= 0) ? 0.5 : -0.5; + // ensure no overfloods + assert(temp >= -32768 && temp <= 32767); + coeffs[i] = (SAMPLETYPE)temp; + } + + // Set coefficients. Use divide factor 14 => divide result by 2^14 = 16384 + pFIR->setCoefficients(coeffs, length, 14); + + delete[] work; + delete[] coeffs; +} + + +// Applies the filter to the given sequence of samples. +// Note : The amount of outputted samples is by value of 'filter length' +// smaller than the amount of input samples. +uint AAFilter::evaluate(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) const +{ + return pFIR->evaluate(dest, src, numSamples, numChannels); +} + + +uint AAFilter::getLength() const +{ + return pFIR->getLength(); +} diff --git a/media/libsoundtouch/src/AAFilter.h b/media/libsoundtouch/src/AAFilter.h new file mode 100644 index 00000000000..7f172842e84 --- /dev/null +++ b/media/libsoundtouch/src/AAFilter.h @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo +/// while maintaining the original pitch by using a time domain WSOLA-like method +/// with several performance-increasing tweaks. +/// +/// Anti-alias filter is used to prevent folding of high frequencies when +/// transposing the sample rate with interpolation. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2008-02-10 08:26:55 -0800 (Sun, 10 Feb 2008) $ +// File revision : $Revision: 4 $ +// +// $Id: AAFilter.h 11 2008-02-10 16:26:55Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef AAFilter_H +#define AAFilter_H + +#include "STTypes.h" + +namespace soundtouch +{ + +class AAFilter +{ +protected: + class FIRFilter *pFIR; + + /// Low-pass filter cut-off frequency, negative = invalid + double cutoffFreq; + + /// num of filter taps + uint length; + + /// Calculate the FIR coefficients realizing the given cutoff-frequency + void calculateCoeffs(); +public: + AAFilter(uint length); + + ~AAFilter(); + + /// Sets new anti-alias filter cut-off edge frequency, scaled to sampling + /// frequency (nyquist frequency = 0.5). The filter will cut off the + /// frequencies than that. + void setCutoffFreq(double newCutoffFreq); + + /// Sets number of FIR filter taps, i.e. ~filter complexity + void setLength(uint newLength); + + uint getLength() const; + + /// Applies the filter to the given sequence of samples. + /// Note : The amount of outputted samples is by value of 'filter length' + /// smaller than the amount of input samples. + uint evaluate(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples, + uint numChannels) const; +}; + +} + +#endif diff --git a/media/libsoundtouch/src/FIFOSampleBuffer.cpp b/media/libsoundtouch/src/FIFOSampleBuffer.cpp new file mode 100644 index 00000000000..bda169276fb --- /dev/null +++ b/media/libsoundtouch/src/FIFOSampleBuffer.cpp @@ -0,0 +1,274 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// A buffer class for temporarily storaging sound samples, operates as a +/// first-in-first-out pipe. +/// +/// Samples are added to the end of the sample buffer with the 'putSamples' +/// function, and are received from the beginning of the buffer by calling +/// the 'receiveSamples' function. The class automatically removes the +/// outputted samples from the buffer, as well as grows the buffer size +/// whenever necessary. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: FIFOSampleBuffer.cpp 143 2012-06-13 19:29:53Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "FIFOSampleBuffer.h" + +using namespace soundtouch; + +// Constructor +FIFOSampleBuffer::FIFOSampleBuffer(int numChannels) +{ + assert(numChannels > 0); + sizeInBytes = 0; // reasonable initial value + buffer = NULL; + bufferUnaligned = NULL; + samplesInBuffer = 0; + bufferPos = 0; + channels = (uint)numChannels; + ensureCapacity(32); // allocate initial capacity +} + + +// destructor +FIFOSampleBuffer::~FIFOSampleBuffer() +{ + delete[] bufferUnaligned; + bufferUnaligned = NULL; + buffer = NULL; +} + + +// Sets number of channels, 1 = mono, 2 = stereo +void FIFOSampleBuffer::setChannels(int numChannels) +{ + uint usedBytes; + + assert(numChannels > 0); + usedBytes = channels * samplesInBuffer; + channels = (uint)numChannels; + samplesInBuffer = usedBytes / channels; +} + + +// if output location pointer 'bufferPos' isn't zero, 'rewinds' the buffer and +// zeroes this pointer by copying samples from the 'bufferPos' pointer +// location on to the beginning of the buffer. +void FIFOSampleBuffer::rewind() +{ + if (buffer && bufferPos) + { + memmove(buffer, ptrBegin(), sizeof(SAMPLETYPE) * channels * samplesInBuffer); + bufferPos = 0; + } +} + + +// Adds 'numSamples' pcs of samples from the 'samples' memory position to +// the sample buffer. +void FIFOSampleBuffer::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + memcpy(ptrEnd(nSamples), samples, sizeof(SAMPLETYPE) * nSamples * channels); + samplesInBuffer += nSamples; +} + + +// Increases the number of samples in the buffer without copying any actual +// samples. +// +// This function is used to update the number of samples in the sample buffer +// when accessing the buffer directly with 'ptrEnd' function. Please be +// careful though! +void FIFOSampleBuffer::putSamples(uint nSamples) +{ + uint req; + + req = samplesInBuffer + nSamples; + ensureCapacity(req); + samplesInBuffer += nSamples; +} + + +// Returns a pointer to the end of the used part of the sample buffer (i.e. +// where the new samples are to be inserted). This function may be used for +// inserting new samples into the sample buffer directly. Please be careful! +// +// Parameter 'slackCapacity' tells the function how much free capacity (in +// terms of samples) there _at least_ should be, in order to the caller to +// succesfully insert all the required samples to the buffer. When necessary, +// the function grows the buffer size to comply with this requirement. +// +// When using this function as means for inserting new samples, also remember +// to increase the sample count afterwards, by calling the +// 'putSamples(numSamples)' function. +SAMPLETYPE *FIFOSampleBuffer::ptrEnd(uint slackCapacity) +{ + ensureCapacity(samplesInBuffer + slackCapacity); + return buffer + samplesInBuffer * channels; +} + + +// Returns a pointer to the beginning of the currently non-outputted samples. +// This function is provided for accessing the output samples directly. +// Please be careful! +// +// When using this function to output samples, also remember to 'remove' the +// outputted samples from the buffer by calling the +// 'receiveSamples(numSamples)' function +SAMPLETYPE *FIFOSampleBuffer::ptrBegin() +{ + assert(buffer); + return buffer + bufferPos * channels; +} + + +// Ensures that the buffer has enought capacity, i.e. space for _at least_ +// 'capacityRequirement' number of samples. The buffer is grown in steps of +// 4 kilobytes to eliminate the need for frequently growing up the buffer, +// as well as to round the buffer size up to the virtual memory page size. +void FIFOSampleBuffer::ensureCapacity(uint capacityRequirement) +{ + SAMPLETYPE *tempUnaligned, *temp; + + if (capacityRequirement > getCapacity()) + { + // enlarge the buffer in 4kbyte steps (round up to next 4k boundary) + sizeInBytes = (capacityRequirement * channels * sizeof(SAMPLETYPE) + 4095) & (uint)-4096; + assert(sizeInBytes % 2 == 0); + tempUnaligned = new SAMPLETYPE[sizeInBytes / sizeof(SAMPLETYPE) + 16 / sizeof(SAMPLETYPE)]; + if (tempUnaligned == NULL) + { + ST_THROW_RT_ERROR("Couldn't allocate memory!\n"); + } + // Align the buffer to begin at 16byte cache line boundary for optimal performance + temp = (SAMPLETYPE *)(((ulong)tempUnaligned + 15) & (ulong)-16); + if (samplesInBuffer) + { + memcpy(temp, ptrBegin(), samplesInBuffer * channels * sizeof(SAMPLETYPE)); + } + delete[] bufferUnaligned; + buffer = temp; + bufferUnaligned = tempUnaligned; + bufferPos = 0; + } + else + { + // simply rewind the buffer (if necessary) + rewind(); + } +} + + +// Returns the current buffer capacity in terms of samples +uint FIFOSampleBuffer::getCapacity() const +{ + return sizeInBytes / (channels * sizeof(SAMPLETYPE)); +} + + +// Returns the number of samples currently in the buffer +uint FIFOSampleBuffer::numSamples() const +{ + return samplesInBuffer; +} + + +// Output samples from beginning of the sample buffer. Copies demanded number +// of samples to output and removes them from the sample buffer. If there +// are less than 'numsample' samples in the buffer, returns all available. +// +// Returns number of samples copied. +uint FIFOSampleBuffer::receiveSamples(SAMPLETYPE *output, uint maxSamples) +{ + uint num; + + num = (maxSamples > samplesInBuffer) ? samplesInBuffer : maxSamples; + + memcpy(output, ptrBegin(), channels * sizeof(SAMPLETYPE) * num); + return receiveSamples(num); +} + + +// Removes samples from the beginning of the sample buffer without copying them +// anywhere. Used to reduce the number of samples in the buffer, when accessing +// the sample buffer with the 'ptrBegin' function. +uint FIFOSampleBuffer::receiveSamples(uint maxSamples) +{ + if (maxSamples >= samplesInBuffer) + { + uint temp; + + temp = samplesInBuffer; + samplesInBuffer = 0; + return temp; + } + + samplesInBuffer -= maxSamples; + bufferPos += maxSamples; + + return maxSamples; +} + + +// Returns nonzero if the sample buffer is empty +int FIFOSampleBuffer::isEmpty() const +{ + return (samplesInBuffer == 0) ? 1 : 0; +} + + +// Clears the sample buffer +void FIFOSampleBuffer::clear() +{ + samplesInBuffer = 0; + bufferPos = 0; +} + + +/// allow trimming (downwards) amount of samples in pipeline. +/// Returns adjusted amount of samples +uint FIFOSampleBuffer::adjustAmountOfSamples(uint numSamples) +{ + if (numSamples < samplesInBuffer) + { + samplesInBuffer = numSamples; + } + return samplesInBuffer; +} + diff --git a/media/libsoundtouch/src/FIFOSampleBuffer.h b/media/libsoundtouch/src/FIFOSampleBuffer.h new file mode 100644 index 00000000000..34702d94c76 --- /dev/null +++ b/media/libsoundtouch/src/FIFOSampleBuffer.h @@ -0,0 +1,178 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// A buffer class for temporarily storaging sound samples, operates as a +/// first-in-first-out pipe. +/// +/// Samples are added to the end of the sample buffer with the 'putSamples' +/// function, and are received from the beginning of the buffer by calling +/// the 'receiveSamples' function. The class automatically removes the +/// output samples from the buffer as well as grows the storage size +/// whenever necessary. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: FIFOSampleBuffer.h 143 2012-06-13 19:29:53Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FIFOSampleBuffer_H +#define FIFOSampleBuffer_H + +#include "FIFOSamplePipe.h" + +namespace soundtouch +{ + +/// Sample buffer working in FIFO (first-in-first-out) principle. The class takes +/// care of storage size adjustment and data moving during input/output operations. +/// +/// Notice that in case of stereo audio, one sample is considered to consist of +/// both channel data. +class FIFOSampleBuffer : public FIFOSamplePipe +{ +private: + /// Sample buffer. + SAMPLETYPE *buffer; + + // Raw unaligned buffer memory. 'buffer' is made aligned by pointing it to first + // 16-byte aligned location of this buffer + SAMPLETYPE *bufferUnaligned; + + /// Sample buffer size in bytes + uint sizeInBytes; + + /// How many samples are currently in buffer. + uint samplesInBuffer; + + /// Channels, 1=mono, 2=stereo. + uint channels; + + /// Current position pointer to the buffer. This pointer is increased when samples are + /// removed from the pipe so that it's necessary to actually rewind buffer (move data) + /// only new data when is put to the pipe. + uint bufferPos; + + /// Rewind the buffer by moving data from position pointed by 'bufferPos' to real + /// beginning of the buffer. + void rewind(); + + /// Ensures that the buffer has capacity for at least this many samples. + void ensureCapacity(uint capacityRequirement); + + /// Returns current capacity. + uint getCapacity() const; + +public: + + /// Constructor + FIFOSampleBuffer(int numChannels = 2 ///< Number of channels, 1=mono, 2=stereo. + ///< Default is stereo. + ); + + /// destructor + ~FIFOSampleBuffer(); + + /// Returns a pointer to the beginning of the output samples. + /// This function is provided for accessing the output samples directly. + /// Please be careful for not to corrupt the book-keeping! + /// + /// When using this function to output samples, also remember to 'remove' the + /// output samples from the buffer by calling the + /// 'receiveSamples(numSamples)' function + virtual SAMPLETYPE *ptrBegin(); + + /// Returns a pointer to the end of the used part of the sample buffer (i.e. + /// where the new samples are to be inserted). This function may be used for + /// inserting new samples into the sample buffer directly. Please be careful + /// not corrupt the book-keeping! + /// + /// When using this function as means for inserting new samples, also remember + /// to increase the sample count afterwards, by calling the + /// 'putSamples(numSamples)' function. + SAMPLETYPE *ptrEnd( + uint slackCapacity ///< How much free capacity (in samples) there _at least_ + ///< should be so that the caller can succesfully insert the + ///< desired samples to the buffer. If necessary, the function + ///< grows the buffer size to comply with this requirement. + ); + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position to + /// the sample buffer. + virtual void putSamples(const SAMPLETYPE *samples, ///< Pointer to samples. + uint numSamples ///< Number of samples to insert. + ); + + /// Adjusts the book-keeping to increase number of samples in the buffer without + /// copying any actual samples. + /// + /// This function is used to update the number of samples in the sample buffer + /// when accessing the buffer directly with 'ptrEnd' function. Please be + /// careful though! + virtual void putSamples(uint numSamples ///< Number of samples been inserted. + ); + + /// Output samples from beginning of the sample buffer. Copies requested samples to + /// output buffer and removes them from the sample buffer. If there are less than + /// 'numsample' samples in the buffer, returns all that available. + /// + /// \return Number of samples returned. + virtual uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples. + uint maxSamples ///< How many samples to receive at max. + ); + + /// Adjusts book-keeping so that given number of samples are removed from beginning of the + /// sample buffer without copying them anywhere. + /// + /// Used to reduce the number of samples in the buffer when accessing the sample buffer directly + /// with 'ptrBegin' function. + virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe. + ); + + /// Returns number of samples currently available. + virtual uint numSamples() const; + + /// Sets number of channels, 1 = mono, 2 = stereo. + void setChannels(int numChannels); + + /// Returns nonzero if there aren't any samples available for outputting. + virtual int isEmpty() const; + + /// Clears all the samples. + virtual void clear(); + + /// allow trimming (downwards) amount of samples in pipeline. + /// Returns adjusted amount of samples + uint adjustAmountOfSamples(uint numSamples); +}; + +} + +#endif diff --git a/media/libsoundtouch/src/FIFOSamplePipe.h b/media/libsoundtouch/src/FIFOSamplePipe.h new file mode 100644 index 00000000000..dde528c8664 --- /dev/null +++ b/media/libsoundtouch/src/FIFOSamplePipe.h @@ -0,0 +1,234 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// 'FIFOSamplePipe' : An abstract base class for classes that manipulate sound +/// samples by operating like a first-in-first-out pipe: New samples are fed +/// into one end of the pipe with the 'putSamples' function, and the processed +/// samples are received from the other end with the 'receiveSamples' function. +/// +/// 'FIFOProcessor' : A base class for classes the do signal processing with +/// the samples while operating like a first-in-first-out pipe. When samples +/// are input with the 'putSamples' function, the class processes them +/// and moves the processed samples to the given 'output' pipe object, which +/// may be either another processing stage, or a fifo sample buffer object. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: FIFOSamplePipe.h 143 2012-06-13 19:29:53Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FIFOSamplePipe_H +#define FIFOSamplePipe_H + +#include +#include +#include "STTypes.h" + +namespace soundtouch +{ + +/// Abstract base class for FIFO (first-in-first-out) sample processing classes. +class FIFOSamplePipe +{ +public: + // virtual default destructor + virtual ~FIFOSamplePipe() {} + + + /// Returns a pointer to the beginning of the output samples. + /// This function is provided for accessing the output samples directly. + /// Please be careful for not to corrupt the book-keeping! + /// + /// When using this function to output samples, also remember to 'remove' the + /// output samples from the buffer by calling the + /// 'receiveSamples(numSamples)' function + virtual SAMPLETYPE *ptrBegin() = 0; + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position to + /// the sample buffer. + virtual void putSamples(const SAMPLETYPE *samples, ///< Pointer to samples. + uint numSamples ///< Number of samples to insert. + ) = 0; + + + // Moves samples from the 'other' pipe instance to this instance. + void moveSamples(FIFOSamplePipe &other ///< Other pipe instance where from the receive the data. + ) + { + int oNumSamples = other.numSamples(); + + putSamples(other.ptrBegin(), oNumSamples); + other.receiveSamples(oNumSamples); + }; + + /// Output samples from beginning of the sample buffer. Copies requested samples to + /// output buffer and removes them from the sample buffer. If there are less than + /// 'numsample' samples in the buffer, returns all that available. + /// + /// \return Number of samples returned. + virtual uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples. + uint maxSamples ///< How many samples to receive at max. + ) = 0; + + /// Adjusts book-keeping so that given number of samples are removed from beginning of the + /// sample buffer without copying them anywhere. + /// + /// Used to reduce the number of samples in the buffer when accessing the sample buffer directly + /// with 'ptrBegin' function. + virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe. + ) = 0; + + /// Returns number of samples currently available. + virtual uint numSamples() const = 0; + + // Returns nonzero if there aren't any samples available for outputting. + virtual int isEmpty() const = 0; + + /// Clears all the samples. + virtual void clear() = 0; + + /// allow trimming (downwards) amount of samples in pipeline. + /// Returns adjusted amount of samples + virtual uint adjustAmountOfSamples(uint numSamples) = 0; + +}; + + + +/// Base-class for sound processing routines working in FIFO principle. With this base +/// class it's easy to implement sound processing stages that can be chained together, +/// so that samples that are fed into beginning of the pipe automatically go through +/// all the processing stages. +/// +/// When samples are input to this class, they're first processed and then put to +/// the FIFO pipe that's defined as output of this class. This output pipe can be +/// either other processing stage or a FIFO sample buffer. +class FIFOProcessor :public FIFOSamplePipe +{ +protected: + /// Internal pipe where processed samples are put. + FIFOSamplePipe *output; + + /// Sets output pipe. + void setOutPipe(FIFOSamplePipe *pOutput) + { + assert(output == NULL); + assert(pOutput != NULL); + output = pOutput; + } + + + /// Constructor. Doesn't define output pipe; it has to be set be + /// 'setOutPipe' function. + FIFOProcessor() + { + output = NULL; + } + + + /// Constructor. Configures output pipe. + FIFOProcessor(FIFOSamplePipe *pOutput ///< Output pipe. + ) + { + output = pOutput; + } + + + /// Destructor. + virtual ~FIFOProcessor() + { + } + + + /// Returns a pointer to the beginning of the output samples. + /// This function is provided for accessing the output samples directly. + /// Please be careful for not to corrupt the book-keeping! + /// + /// When using this function to output samples, also remember to 'remove' the + /// output samples from the buffer by calling the + /// 'receiveSamples(numSamples)' function + virtual SAMPLETYPE *ptrBegin() + { + return output->ptrBegin(); + } + +public: + + /// Output samples from beginning of the sample buffer. Copies requested samples to + /// output buffer and removes them from the sample buffer. If there are less than + /// 'numsample' samples in the buffer, returns all that available. + /// + /// \return Number of samples returned. + virtual uint receiveSamples(SAMPLETYPE *outBuffer, ///< Buffer where to copy output samples. + uint maxSamples ///< How many samples to receive at max. + ) + { + return output->receiveSamples(outBuffer, maxSamples); + } + + + /// Adjusts book-keeping so that given number of samples are removed from beginning of the + /// sample buffer without copying them anywhere. + /// + /// Used to reduce the number of samples in the buffer when accessing the sample buffer directly + /// with 'ptrBegin' function. + virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe. + ) + { + return output->receiveSamples(maxSamples); + } + + + /// Returns number of samples currently available. + virtual uint numSamples() const + { + return output->numSamples(); + } + + + /// Returns nonzero if there aren't any samples available for outputting. + virtual int isEmpty() const + { + return output->isEmpty(); + } + + /// allow trimming (downwards) amount of samples in pipeline. + /// Returns adjusted amount of samples + virtual uint adjustAmountOfSamples(uint numSamples) + { + return output->adjustAmountOfSamples(numSamples); + } + +}; + +} + +#endif diff --git a/media/libsoundtouch/src/FIRFilter.cpp b/media/libsoundtouch/src/FIRFilter.cpp new file mode 100644 index 00000000000..3ba568f642f --- /dev/null +++ b/media/libsoundtouch/src/FIRFilter.cpp @@ -0,0 +1,259 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// General FIR digital filter routines with MMX optimization. +/// +/// Note : MMX optimized functions reside in a separate, platform-specific file, +/// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2011-09-02 11:56:11 -0700 (Fri, 02 Sep 2011) $ +// File revision : $Revision: 4 $ +// +// $Id: FIRFilter.cpp 131 2011-09-02 18:56:11Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "FIRFilter.h" +#include "cpu_detect.h" + +using namespace soundtouch; + +/***************************************************************************** + * + * Implementation of the class 'FIRFilter' + * + *****************************************************************************/ + +FIRFilter::FIRFilter() +{ + resultDivFactor = 0; + resultDivider = 0; + length = 0; + lengthDiv8 = 0; + filterCoeffs = NULL; +} + + +FIRFilter::~FIRFilter() +{ + delete[] filterCoeffs; +} + +// Usual C-version of the filter routine for stereo sound +uint FIRFilter::evaluateFilterStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples) const +{ + uint i, j, end; + LONG_SAMPLETYPE suml, sumr; +#ifdef SOUNDTOUCH_FLOAT_SAMPLES + // when using floating point samples, use a scaler instead of a divider + // because division is much slower operation than multiplying. + double dScaler = 1.0 / (double)resultDivider; +#endif + + assert(length != 0); + assert(src != NULL); + assert(dest != NULL); + assert(filterCoeffs != NULL); + + end = 2 * (numSamples - length); + + for (j = 0; j < end; j += 2) + { + const SAMPLETYPE *ptr; + + suml = sumr = 0; + ptr = src + j; + + for (i = 0; i < length; i += 4) + { + // loop is unrolled by factor of 4 here for efficiency + suml += ptr[2 * i + 0] * filterCoeffs[i + 0] + + ptr[2 * i + 2] * filterCoeffs[i + 1] + + ptr[2 * i + 4] * filterCoeffs[i + 2] + + ptr[2 * i + 6] * filterCoeffs[i + 3]; + sumr += ptr[2 * i + 1] * filterCoeffs[i + 0] + + ptr[2 * i + 3] * filterCoeffs[i + 1] + + ptr[2 * i + 5] * filterCoeffs[i + 2] + + ptr[2 * i + 7] * filterCoeffs[i + 3]; + } + +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + suml >>= resultDivFactor; + sumr >>= resultDivFactor; + // saturate to 16 bit integer limits + suml = (suml < -32768) ? -32768 : (suml > 32767) ? 32767 : suml; + // saturate to 16 bit integer limits + sumr = (sumr < -32768) ? -32768 : (sumr > 32767) ? 32767 : sumr; +#else + suml *= dScaler; + sumr *= dScaler; +#endif // SOUNDTOUCH_INTEGER_SAMPLES + dest[j] = (SAMPLETYPE)suml; + dest[j + 1] = (SAMPLETYPE)sumr; + } + return numSamples - length; +} + + + + +// Usual C-version of the filter routine for mono sound +uint FIRFilter::evaluateFilterMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples) const +{ + uint i, j, end; + LONG_SAMPLETYPE sum; +#ifdef SOUNDTOUCH_FLOAT_SAMPLES + // when using floating point samples, use a scaler instead of a divider + // because division is much slower operation than multiplying. + double dScaler = 1.0 / (double)resultDivider; +#endif + + + assert(length != 0); + + end = numSamples - length; + for (j = 0; j < end; j ++) + { + sum = 0; + for (i = 0; i < length; i += 4) + { + // loop is unrolled by factor of 4 here for efficiency + sum += src[i + 0] * filterCoeffs[i + 0] + + src[i + 1] * filterCoeffs[i + 1] + + src[i + 2] * filterCoeffs[i + 2] + + src[i + 3] * filterCoeffs[i + 3]; + } +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + sum >>= resultDivFactor; + // saturate to 16 bit integer limits + sum = (sum < -32768) ? -32768 : (sum > 32767) ? 32767 : sum; +#else + sum *= dScaler; +#endif // SOUNDTOUCH_INTEGER_SAMPLES + dest[j] = (SAMPLETYPE)sum; + src ++; + } + return end; +} + + +// Set filter coeffiecients and length. +// +// Throws an exception if filter length isn't divisible by 8 +void FIRFilter::setCoefficients(const SAMPLETYPE *coeffs, uint newLength, uint uResultDivFactor) +{ + assert(newLength > 0); + if (newLength % 8) ST_THROW_RT_ERROR("FIR filter length not divisible by 8"); + + lengthDiv8 = newLength / 8; + length = lengthDiv8 * 8; + assert(length == newLength); + + resultDivFactor = uResultDivFactor; + resultDivider = (SAMPLETYPE)::pow(2.0, (int)resultDivFactor); + + delete[] filterCoeffs; + filterCoeffs = new SAMPLETYPE[length]; + memcpy(filterCoeffs, coeffs, length * sizeof(SAMPLETYPE)); +} + + +uint FIRFilter::getLength() const +{ + return length; +} + + + +// Applies the filter to the given sequence of samples. +// +// Note : The amount of outputted samples is by value of 'filter_length' +// smaller than the amount of input samples. +uint FIRFilter::evaluate(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) const +{ + assert(numChannels == 1 || numChannels == 2); + + assert(length > 0); + assert(lengthDiv8 * 8 == length); + if (numSamples < length) return 0; + if (numChannels == 2) + { + return evaluateFilterStereo(dest, src, numSamples); + } else { + return evaluateFilterMono(dest, src, numSamples); + } +} + + + +// Operator 'new' is overloaded so that it automatically creates a suitable instance +// depending on if we've a MMX-capable CPU available or not. +void * FIRFilter::operator new(size_t s) +{ + // Notice! don't use "new FIRFilter" directly, use "newInstance" to create a new instance instead! + ST_THROW_RT_ERROR("Error in FIRFilter::new: Don't use 'new FIRFilter', use 'newInstance' member instead!"); + return newInstance(); +} + + +FIRFilter * FIRFilter::newInstance() +{ + uint uExtensions; + + uExtensions = detectCPUextensions(); + + // Check if MMX/SSE instruction set extensions supported by CPU + +#ifdef SOUNDTOUCH_ALLOW_MMX + // MMX routines available only with integer sample types + if (uExtensions & SUPPORT_MMX) + { + return ::new FIRFilterMMX; + } + else +#endif // SOUNDTOUCH_ALLOW_MMX + +#ifdef SOUNDTOUCH_ALLOW_SSE + if (uExtensions & SUPPORT_SSE) + { + // SSE support + return ::new FIRFilterSSE; + } + else +#endif // SOUNDTOUCH_ALLOW_SSE + + { + // ISA optimizations not supported, use plain C version + return ::new FIRFilter; + } +} diff --git a/media/libsoundtouch/src/FIRFilter.h b/media/libsoundtouch/src/FIRFilter.h new file mode 100644 index 00000000000..25ede16a8aa --- /dev/null +++ b/media/libsoundtouch/src/FIRFilter.h @@ -0,0 +1,145 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// General FIR digital filter routines with MMX optimization. +/// +/// Note : MMX optimized functions reside in a separate, platform-specific file, +/// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2011-02-13 11:13:57 -0800 (Sun, 13 Feb 2011) $ +// File revision : $Revision: 4 $ +// +// $Id: FIRFilter.h 104 2011-02-13 19:13:57Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FIRFilter_H +#define FIRFilter_H + +#include +#include "STTypes.h" + +namespace soundtouch +{ + +class FIRFilter +{ +protected: + // Number of FIR filter taps + uint length; + // Number of FIR filter taps divided by 8 + uint lengthDiv8; + + // Result divider factor in 2^k format + uint resultDivFactor; + + // Result divider value. + SAMPLETYPE resultDivider; + + // Memory for filter coefficients + SAMPLETYPE *filterCoeffs; + + virtual uint evaluateFilterStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) const; + virtual uint evaluateFilterMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) const; + +public: + FIRFilter(); + virtual ~FIRFilter(); + + /// Operator 'new' is overloaded so that it automatically creates a suitable instance + /// depending on if we've a MMX-capable CPU available or not. + static void * operator new(size_t s); + + static FIRFilter *newInstance(); + + /// Applies the filter to the given sequence of samples. + /// Note : The amount of outputted samples is by value of 'filter_length' + /// smaller than the amount of input samples. + /// + /// \return Number of samples copied to 'dest'. + uint evaluate(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples, + uint numChannels) const; + + uint getLength() const; + + virtual void setCoefficients(const SAMPLETYPE *coeffs, + uint newLength, + uint uResultDivFactor); +}; + + +// Optional subclasses that implement CPU-specific optimizations: + +#ifdef SOUNDTOUCH_ALLOW_MMX + +/// Class that implements MMX optimized functions exclusive for 16bit integer samples type. + class FIRFilterMMX : public FIRFilter + { + protected: + short *filterCoeffsUnalign; + short *filterCoeffsAlign; + + virtual uint evaluateFilterStereo(short *dest, const short *src, uint numSamples) const; + public: + FIRFilterMMX(); + ~FIRFilterMMX(); + + virtual void setCoefficients(const short *coeffs, uint newLength, uint uResultDivFactor); + }; + +#endif // SOUNDTOUCH_ALLOW_MMX + + +#ifdef SOUNDTOUCH_ALLOW_SSE + /// Class that implements SSE optimized functions exclusive for floating point samples type. + class FIRFilterSSE : public FIRFilter + { + protected: + float *filterCoeffsUnalign; + float *filterCoeffsAlign; + + virtual uint evaluateFilterStereo(float *dest, const float *src, uint numSamples) const; + public: + FIRFilterSSE(); + ~FIRFilterSSE(); + + virtual void setCoefficients(const float *coeffs, uint newLength, uint uResultDivFactor); + }; + +#endif // SOUNDTOUCH_ALLOW_SSE + +} + +#endif // FIRFilter_H diff --git a/media/libsoundtouch/src/Makefile.in b/media/libsoundtouch/src/Makefile.in new file mode 100644 index 00000000000..965d04318f8 --- /dev/null +++ b/media/libsoundtouch/src/Makefile.in @@ -0,0 +1,69 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = soundtouch +LIBRARY_NAME = soundtouch +SHORT_LIBNAME = soundt +FORCE_SHARED_LIB = 1 +VISIBILITY_FLAGS = +EXPORTS_NAMESPACES = soundtouch + +ifeq ($(OS_ARCH),WINNT) +ifndef GNU_CC +RCFILE = $(srcdir)/soundtouch.rc +RESFILE = $(srcdir)/soundtouch.res +endif +endif + + +EXTRA_DSO_LDOPTS += $(MOZALLOC_LIB) + +# Use abort() instead of exception in SoundTouch. +DEFINES += -DST_NO_EXCEPTION_HANDLING=1 + +EXPORTS_soundtouch = SoundTouch.h \ + STTypes.h \ + FIFOSamplePipe.h \ + soundtouch_config.h \ + $(NULL) + +CPPSRCS = AAFilter.cpp \ + cpu_detect_x86.cpp \ + FIFOSampleBuffer.cpp \ + FIRFilter.cpp \ + RateTransposer.cpp \ + SoundTouch.cpp \ + TDStretch.cpp \ + $(NULL) + +ifneq (,$(INTEL_ARCHITECTURE)) +ifdef MOZ_SAMPLE_TYPE_FLOAT32 +CPPSRCS += sse_optimized.cpp +else +CPPSRCS += mmx_optimized.cpp +endif +endif + +SOUNDTOUCH_LIBS = $(DLL_PREFIX)soundtouch$(DLL_SUFFIX) + +include $(topsrcdir)/config/rules.mk + +ifneq (,$(INTEL_ARCHITECTURE)) +ifdef GNU_CC +mmx_optimized.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 +sse_optimized.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 +endif +ifdef SOLARIS_SUNPRO_CXX +mmx_optimized.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 +sse_optimized.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 +endif +endif + diff --git a/media/libsoundtouch/src/RateTransposer.cpp b/media/libsoundtouch/src/RateTransposer.cpp new file mode 100644 index 00000000000..6fb8a1eaeac --- /dev/null +++ b/media/libsoundtouch/src/RateTransposer.cpp @@ -0,0 +1,626 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sample rate transposer. Changes sample rate by using linear interpolation +/// together with anti-alias filtering (first order interpolation with anti- +/// alias filtering should be quite adequate for this application) +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2011-09-02 11:56:11 -0700 (Fri, 02 Sep 2011) $ +// File revision : $Revision: 4 $ +// +// $Id: RateTransposer.cpp 131 2011-09-02 18:56:11Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "RateTransposer.h" +#include "AAFilter.h" + +using namespace soundtouch; + + +/// A linear samplerate transposer class that uses integer arithmetics. +/// for the transposing. +class RateTransposerInteger : public RateTransposer +{ +protected: + int iSlopeCount; + int iRate; + SAMPLETYPE sPrevSampleL, sPrevSampleR; + + virtual void resetRegisters(); + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + +public: + RateTransposerInteger(); + virtual ~RateTransposerInteger(); + + /// Sets new target rate. Normal rate = 1.0, smaller values represent slower + /// rate, larger faster rates. + virtual void setRate(float newRate); + +}; + + +/// A linear samplerate transposer class that uses floating point arithmetics +/// for the transposing. +class RateTransposerFloat : public RateTransposer +{ +protected: + float fSlopeCount; + SAMPLETYPE sPrevSampleL, sPrevSampleR; + + virtual void resetRegisters(); + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + +public: + RateTransposerFloat(); + virtual ~RateTransposerFloat(); +}; + + + + +// Operator 'new' is overloaded so that it automatically creates a suitable instance +// depending on if we've a MMX/SSE/etc-capable CPU available or not. +void * RateTransposer::operator new(size_t s) +{ + ST_THROW_RT_ERROR("Error in RateTransoser::new: don't use \"new TDStretch\" directly, use \"newInstance\" to create a new instance instead!"); + return newInstance(); +} + + +RateTransposer *RateTransposer::newInstance() +{ +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + return ::new RateTransposerInteger; +#else + return ::new RateTransposerFloat; +#endif +} + + +// Constructor +RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer) +{ + numChannels = 2; + bUseAAFilter = true; + fRate = 0; + + // Instantiates the anti-alias filter with default tap length + // of 32 + pAAFilter = new AAFilter(32); +} + + + +RateTransposer::~RateTransposer() +{ + delete pAAFilter; +} + + + +/// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable +void RateTransposer::enableAAFilter(bool newMode) +{ + bUseAAFilter = newMode; +} + + +/// Returns nonzero if anti-alias filter is enabled. +bool RateTransposer::isAAFilterEnabled() const +{ + return bUseAAFilter; +} + + +AAFilter *RateTransposer::getAAFilter() +{ + return pAAFilter; +} + + + +// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower +// iRate, larger faster iRates. +void RateTransposer::setRate(float newRate) +{ + double fCutoff; + + fRate = newRate; + + // design a new anti-alias filter + if (newRate > 1.0f) + { + fCutoff = 0.5f / newRate; + } + else + { + fCutoff = 0.5f * newRate; + } + pAAFilter->setCutoffFreq(fCutoff); +} + + +// Outputs as many samples of the 'outputBuffer' as possible, and if there's +// any room left, outputs also as many of the incoming samples as possible. +// The goal is to drive the outputBuffer empty. +// +// It's allowed for 'output' and 'input' parameters to point to the same +// memory position. +/* +void RateTransposer::flushStoreBuffer() +{ + if (storeBuffer.isEmpty()) return; + + outputBuffer.moveSamples(storeBuffer); +} +*/ + + +// Adds 'nSamples' pcs of samples from the 'samples' memory position into +// the input of the object. +void RateTransposer::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + processSamples(samples, nSamples); +} + + + +// Transposes up the sample rate, causing the observed playback 'rate' of the +// sound to decrease +void RateTransposer::upsample(const SAMPLETYPE *src, uint nSamples) +{ + uint count, sizeTemp, num; + + // If the parameter 'uRate' value is smaller than 'SCALE', first transpose + // the samples and then apply the anti-alias filter to remove aliasing. + + // First check that there's enough room in 'storeBuffer' + // (+16 is to reserve some slack in the destination buffer) + sizeTemp = (uint)((float)nSamples / fRate + 16.0f); + + // Transpose the samples, store the result into the end of "storeBuffer" + count = transpose(storeBuffer.ptrEnd(sizeTemp), src, nSamples); + storeBuffer.putSamples(count); + + // Apply the anti-alias filter to samples in "store output", output the + // result to "dest" + num = storeBuffer.numSamples(); + count = pAAFilter->evaluate(outputBuffer.ptrEnd(num), + storeBuffer.ptrBegin(), num, (uint)numChannels); + outputBuffer.putSamples(count); + + // Remove the processed samples from "storeBuffer" + storeBuffer.receiveSamples(count); +} + + +// Transposes down the sample rate, causing the observed playback 'rate' of the +// sound to increase +void RateTransposer::downsample(const SAMPLETYPE *src, uint nSamples) +{ + uint count, sizeTemp; + + // If the parameter 'uRate' value is larger than 'SCALE', first apply the + // anti-alias filter to remove high frequencies (prevent them from folding + // over the lover frequencies), then transpose. + + // Add the new samples to the end of the storeBuffer + storeBuffer.putSamples(src, nSamples); + + // Anti-alias filter the samples to prevent folding and output the filtered + // data to tempBuffer. Note : because of the FIR filter length, the + // filtering routine takes in 'filter_length' more samples than it outputs. + assert(tempBuffer.isEmpty()); + sizeTemp = storeBuffer.numSamples(); + + count = pAAFilter->evaluate(tempBuffer.ptrEnd(sizeTemp), + storeBuffer.ptrBegin(), sizeTemp, (uint)numChannels); + + if (count == 0) return; + + // Remove the filtered samples from 'storeBuffer' + storeBuffer.receiveSamples(count); + + // Transpose the samples (+16 is to reserve some slack in the destination buffer) + sizeTemp = (uint)((float)nSamples / fRate + 16.0f); + count = transpose(outputBuffer.ptrEnd(sizeTemp), tempBuffer.ptrBegin(), count); + outputBuffer.putSamples(count); +} + + +// Transposes sample rate by applying anti-alias filter to prevent folding. +// Returns amount of samples returned in the "dest" buffer. +// The maximum amount of samples that can be returned at a time is set by +// the 'set_returnBuffer_size' function. +void RateTransposer::processSamples(const SAMPLETYPE *src, uint nSamples) +{ + uint count; + uint sizeReq; + + if (nSamples == 0) return; + assert(pAAFilter); + + // If anti-alias filter is turned off, simply transpose without applying + // the filter + if (bUseAAFilter == false) + { + sizeReq = (uint)((float)nSamples / fRate + 1.0f); + count = transpose(outputBuffer.ptrEnd(sizeReq), src, nSamples); + outputBuffer.putSamples(count); + return; + } + + // Transpose with anti-alias filter + if (fRate < 1.0f) + { + upsample(src, nSamples); + } + else + { + downsample(src, nSamples); + } +} + + +// Transposes the sample rate of the given samples using linear interpolation. +// Returns the number of samples returned in the "dest" buffer +inline uint RateTransposer::transpose(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + if (numChannels == 2) + { + return transposeStereo(dest, src, nSamples); + } + else + { + return transposeMono(dest, src, nSamples); + } +} + + +// Sets the number of channels, 1 = mono, 2 = stereo +void RateTransposer::setChannels(int nChannels) +{ + assert(nChannels > 0); + if (numChannels == nChannels) return; + + assert(nChannels == 1 || nChannels == 2); + numChannels = nChannels; + + storeBuffer.setChannels(numChannels); + tempBuffer.setChannels(numChannels); + outputBuffer.setChannels(numChannels); + + // Inits the linear interpolation registers + resetRegisters(); +} + + +// Clears all the samples in the object +void RateTransposer::clear() +{ + outputBuffer.clear(); + storeBuffer.clear(); +} + + +// Returns nonzero if there aren't any samples available for outputting. +int RateTransposer::isEmpty() const +{ + int res; + + res = FIFOProcessor::isEmpty(); + if (res == 0) return 0; + return storeBuffer.isEmpty(); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// RateTransposerInteger - integer arithmetic implementation +// + +/// fixed-point interpolation routine precision +#define SCALE 65536 + +// Constructor +RateTransposerInteger::RateTransposerInteger() : RateTransposer() +{ + // Notice: use local function calling syntax for sake of clarity, + // to indicate the fact that C++ constructor can't call virtual functions. + RateTransposerInteger::resetRegisters(); + RateTransposerInteger::setRate(1.0f); +} + + +RateTransposerInteger::~RateTransposerInteger() +{ +} + + +void RateTransposerInteger::resetRegisters() +{ + iSlopeCount = 0; + sPrevSampleL = + sPrevSampleR = 0; +} + + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Mono' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerInteger::transposeMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int i, used; + LONG_SAMPLETYPE temp, vol1; + + if (nSamples == 0) return 0; // no samples, no work + + used = 0; + i = 0; + + // Process the last sample saved from the previous call first... + while (iSlopeCount <= SCALE) + { + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = vol1 * sPrevSampleL + iSlopeCount * src[0]; + dest[i] = (SAMPLETYPE)(temp / SCALE); + i++; + iSlopeCount += iRate; + } + // now always (iSlopeCount > SCALE) + iSlopeCount -= SCALE; + + while (1) + { + while (iSlopeCount > SCALE) + { + iSlopeCount -= SCALE; + used ++; + if (used >= nSamples - 1) goto end; + } + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = src[used] * vol1 + iSlopeCount * src[used + 1]; + dest[i] = (SAMPLETYPE)(temp / SCALE); + + i++; + iSlopeCount += iRate; + } +end: + // Store the last sample for the next round + sPrevSampleL = src[nSamples - 1]; + + return i; +} + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Stereo' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerInteger::transposeStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int srcPos, i, used; + LONG_SAMPLETYPE temp, vol1; + + if (nSamples == 0) return 0; // no samples, no work + + used = 0; + i = 0; + + // Process the last sample saved from the sPrevSampleLious call first... + while (iSlopeCount <= SCALE) + { + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = vol1 * sPrevSampleL + iSlopeCount * src[0]; + dest[2 * i] = (SAMPLETYPE)(temp / SCALE); + temp = vol1 * sPrevSampleR + iSlopeCount * src[1]; + dest[2 * i + 1] = (SAMPLETYPE)(temp / SCALE); + i++; + iSlopeCount += iRate; + } + // now always (iSlopeCount > SCALE) + iSlopeCount -= SCALE; + + while (1) + { + while (iSlopeCount > SCALE) + { + iSlopeCount -= SCALE; + used ++; + if (used >= nSamples - 1) goto end; + } + srcPos = 2 * used; + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = src[srcPos] * vol1 + iSlopeCount * src[srcPos + 2]; + dest[2 * i] = (SAMPLETYPE)(temp / SCALE); + temp = src[srcPos + 1] * vol1 + iSlopeCount * src[srcPos + 3]; + dest[2 * i + 1] = (SAMPLETYPE)(temp / SCALE); + + i++; + iSlopeCount += iRate; + } +end: + // Store the last sample for the next round + sPrevSampleL = src[2 * nSamples - 2]; + sPrevSampleR = src[2 * nSamples - 1]; + + return i; +} + + +// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower +// iRate, larger faster iRates. +void RateTransposerInteger::setRate(float newRate) +{ + iRate = (int)(newRate * SCALE + 0.5f); + RateTransposer::setRate(newRate); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// RateTransposerFloat - floating point arithmetic implementation +// +////////////////////////////////////////////////////////////////////////////// + +// Constructor +RateTransposerFloat::RateTransposerFloat() : RateTransposer() +{ + // Notice: use local function calling syntax for sake of clarity, + // to indicate the fact that C++ constructor can't call virtual functions. + RateTransposerFloat::resetRegisters(); + RateTransposerFloat::setRate(1.0f); +} + + +RateTransposerFloat::~RateTransposerFloat() +{ +} + + +void RateTransposerFloat::resetRegisters() +{ + fSlopeCount = 0; + sPrevSampleL = + sPrevSampleR = 0; +} + + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Mono' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerFloat::transposeMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int i, used; + + used = 0; + i = 0; + + // Process the last sample saved from the previous call first... + while (fSlopeCount <= 1.0f) + { + dest[i] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleL + fSlopeCount * src[0]); + i++; + fSlopeCount += fRate; + } + fSlopeCount -= 1.0f; + + if (nSamples > 1) + { + while (1) + { + while (fSlopeCount > 1.0f) + { + fSlopeCount -= 1.0f; + used ++; + if (used >= nSamples - 1) goto end; + } + dest[i] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[used] + fSlopeCount * src[used + 1]); + i++; + fSlopeCount += fRate; + } + } +end: + // Store the last sample for the next round + sPrevSampleL = src[nSamples - 1]; + + return i; +} + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Mono' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerFloat::transposeStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int srcPos, i, used; + + if (nSamples == 0) return 0; // no samples, no work + + used = 0; + i = 0; + + // Process the last sample saved from the sPrevSampleLious call first... + while (fSlopeCount <= 1.0f) + { + dest[2 * i] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleL + fSlopeCount * src[0]); + dest[2 * i + 1] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleR + fSlopeCount * src[1]); + i++; + fSlopeCount += fRate; + } + // now always (iSlopeCount > 1.0f) + fSlopeCount -= 1.0f; + + if (nSamples > 1) + { + while (1) + { + while (fSlopeCount > 1.0f) + { + fSlopeCount -= 1.0f; + used ++; + if (used >= nSamples - 1) goto end; + } + srcPos = 2 * used; + + dest[2 * i] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[srcPos] + + fSlopeCount * src[srcPos + 2]); + dest[2 * i + 1] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[srcPos + 1] + + fSlopeCount * src[srcPos + 3]); + + i++; + fSlopeCount += fRate; + } + } +end: + // Store the last sample for the next round + sPrevSampleL = src[2 * nSamples - 2]; + sPrevSampleR = src[2 * nSamples - 1]; + + return i; +} diff --git a/media/libsoundtouch/src/RateTransposer.h b/media/libsoundtouch/src/RateTransposer.h new file mode 100644 index 00000000000..c2256210d92 --- /dev/null +++ b/media/libsoundtouch/src/RateTransposer.h @@ -0,0 +1,159 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sample rate transposer. Changes sample rate by using linear interpolation +/// together with anti-alias filtering (first order interpolation with anti- +/// alias filtering should be quite adequate for this application). +/// +/// Use either of the derived classes of 'RateTransposerInteger' or +/// 'RateTransposerFloat' for corresponding integer/floating point tranposing +/// algorithm implementation. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2009-02-21 08:00:14 -0800 (Sat, 21 Feb 2009) $ +// File revision : $Revision: 4 $ +// +// $Id: RateTransposer.h 63 2009-02-21 16:00:14Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef RateTransposer_H +#define RateTransposer_H + +#include +#include "AAFilter.h" +#include "FIFOSamplePipe.h" +#include "FIFOSampleBuffer.h" + +#include "STTypes.h" + +namespace soundtouch +{ + +/// A common linear samplerate transposer class. +/// +/// Note: Use function "RateTransposer::newInstance()" to create a new class +/// instance instead of the "new" operator; that function automatically +/// chooses a correct implementation depending on if integer or floating +/// arithmetics are to be used. +class RateTransposer : public FIFOProcessor +{ +protected: + /// Anti-alias filter object + AAFilter *pAAFilter; + + float fRate; + + int numChannels; + + /// Buffer for collecting samples to feed the anti-alias filter between + /// two batches + FIFOSampleBuffer storeBuffer; + + /// Buffer for keeping samples between transposing & anti-alias filter + FIFOSampleBuffer tempBuffer; + + /// Output sample buffer + FIFOSampleBuffer outputBuffer; + + bool bUseAAFilter; + + virtual void resetRegisters() = 0; + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) = 0; + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) = 0; + inline uint transpose(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + + void downsample(const SAMPLETYPE *src, + uint numSamples); + void upsample(const SAMPLETYPE *src, + uint numSamples); + + /// Transposes sample rate by applying anti-alias filter to prevent folding. + /// Returns amount of samples returned in the "dest" buffer. + /// The maximum amount of samples that can be returned at a time is set by + /// the 'set_returnBuffer_size' function. + void processSamples(const SAMPLETYPE *src, + uint numSamples); + + +public: + RateTransposer(); + virtual ~RateTransposer(); + + /// Operator 'new' is overloaded so that it automatically creates a suitable instance + /// depending on if we're to use integer or floating point arithmetics. + static void *operator new(size_t s); + + /// Use this function instead of "new" operator to create a new instance of this class. + /// This function automatically chooses a correct implementation, depending on if + /// integer ot floating point arithmetics are to be used. + static RateTransposer *newInstance(); + + /// Returns the output buffer object + FIFOSamplePipe *getOutput() { return &outputBuffer; }; + + /// Returns the store buffer object + FIFOSamplePipe *getStore() { return &storeBuffer; }; + + /// Return anti-alias filter object + AAFilter *getAAFilter(); + + /// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable + void enableAAFilter(bool newMode); + + /// Returns nonzero if anti-alias filter is enabled. + bool isAAFilterEnabled() const; + + /// Sets new target rate. Normal rate = 1.0, smaller values represent slower + /// rate, larger faster rates. + virtual void setRate(float newRate); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(int channels); + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position into + /// the input of the object. + void putSamples(const SAMPLETYPE *samples, uint numSamples); + + /// Clears all the samples in the object + void clear(); + + /// Returns nonzero if there aren't any samples available for outputting. + int isEmpty() const; +}; + +} + +#endif diff --git a/media/libsoundtouch/src/STTypes.h b/media/libsoundtouch/src/STTypes.h new file mode 100644 index 00000000000..bfd08f2da0e --- /dev/null +++ b/media/libsoundtouch/src/STTypes.h @@ -0,0 +1,159 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Common type definitions for SoundTouch audio processing library. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 10:01:42 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 3 $ +// +// $Id: STTypes.h 136 2012-04-01 17:01:42Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef STTypes_H +#define STTypes_H + +typedef unsigned int uint; +typedef unsigned long ulong; + +#include "soundtouch_config.h" + +#ifdef WIN32 +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + +namespace soundtouch +{ + /// Activate these undef's to overrule the possible sampletype + /// setting inherited from some other header file: + //#undef SOUNDTOUCH_INTEGER_SAMPLES + //#undef SOUNDTOUCH_FLOAT_SAMPLES + + #if !(SOUNDTOUCH_INTEGER_SAMPLES || SOUNDTOUCH_FLOAT_SAMPLES) + + /// Choose either 32bit floating point or 16bit integer sampletype + /// by choosing one of the following defines, unless this selection + /// has already been done in some other file. + //// + /// Notes: + /// - In Windows environment, choose the sample format with the + /// following defines. + /// - In GNU environment, the floating point samples are used by + /// default, but integer samples can be chosen by giving the + /// following switch to the configure script: + /// ./configure --enable-integer-samples + /// However, if you still prefer to select the sample format here + /// also in GNU environment, then please #undef the INTEGER_SAMPLE + /// and FLOAT_SAMPLE defines first as in comments above. + //#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples + #define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples + + #endif + + #if (_M_IX86 || __i386__ || __x86_64__ || _M_X64) + /// Define this to allow X86-specific assembler/intrinsic optimizations. + /// Notice that library contains also usual C++ versions of each of these + /// these routines, so if you're having difficulties getting the optimized + /// routines compiled for whatever reason, you may disable these optimizations + /// to make the library compile. + + #define SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS 1 + + /// In GNU environment, allow the user to override this setting by + /// giving the following switch to the configure script: + /// ./configure --disable-x86-optimizations + /// ./configure --enable-x86-optimizations=no + #ifdef SOUNDTOUCH_DISABLE_X86_OPTIMIZATIONS + #undef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + #endif + #else + /// Always disable optimizations when not using a x86 systems. + #undef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + + #endif + + // If defined, allows the SIMD-optimized routines to take minor shortcuts + // for improved performance. Undefine to require faithfully similar SIMD + // calculations as in normal C implementation. + #define SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION 1 + + + #ifdef SOUNDTOUCH_INTEGER_SAMPLES + // 16bit integer sample type + typedef short SAMPLETYPE; + // data type for sample accumulation: Use 32bit integer to prevent overflows + typedef long LONG_SAMPLETYPE; + + #ifdef SOUNDTOUCH_FLOAT_SAMPLES + // check that only one sample type is defined + #error "conflicting sample types defined" + #endif // SOUNDTOUCH_FLOAT_SAMPLES + + #ifdef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + // Allow MMX optimizations + #define SOUNDTOUCH_ALLOW_MMX 1 + #endif + + #else + + // floating point samples + typedef float SAMPLETYPE; + // data type for sample accumulation: Use double to utilize full precision. + typedef double LONG_SAMPLETYPE; + + #ifdef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + // Allow SSE optimizations + #define SOUNDTOUCH_ALLOW_SSE 1 + #endif + + #endif // SOUNDTOUCH_INTEGER_SAMPLES + +} + +// define ST_NO_EXCEPTION_HANDLING switch to disable throwing std exceptions: +#define ST_NO_EXCEPTION_HANDLING 1 +#ifdef ST_NO_EXCEPTION_HANDLING + // Exceptions disabled. Throw asserts instead if enabled. + #include + #define ST_THROW_RT_ERROR(x) {assert((const char *)x);} +#else + // use c++ standard exceptions + #include + #define ST_THROW_RT_ERROR(x) {throw std::runtime_error(x);} +#endif + +// When this #define is active, eliminates a clicking sound when the "rate" or "pitch" +// parameter setting crosses from value <1 to >=1 or vice versa during processing. +// Default is off as such crossover is untypical case and involves a slight sound +// quality compromise. +//#define SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER 1 + +#endif diff --git a/media/libsoundtouch/src/SoundTouch.cpp b/media/libsoundtouch/src/SoundTouch.cpp new file mode 100644 index 00000000000..46de76cb2d4 --- /dev/null +++ b/media/libsoundtouch/src/SoundTouch.cpp @@ -0,0 +1,501 @@ +////////////////////////////////////////////////////////////////////////////// +/// +/// SoundTouch - main class for tempo/pitch/rate adjusting routines. +/// +/// Notes: +/// - Initialize the SoundTouch object instance by setting up the sound stream +/// parameters with functions 'setSampleRate' and 'setChannels', then set +/// desired tempo/pitch/rate settings with the corresponding functions. +/// +/// - The SoundTouch class behaves like a first-in-first-out pipeline: The +/// samples that are to be processed are fed into one of the pipe by calling +/// function 'putSamples', while the ready processed samples can be read +/// from the other end of the pipeline with function 'receiveSamples'. +/// +/// - The SoundTouch processing classes require certain sized 'batches' of +/// samples in order to process the sound. For this reason the classes buffer +/// incoming samples until there are enough of samples available for +/// processing, then they carry out the processing step and consequently +/// make the processed samples available for outputting. +/// +/// - For the above reason, the processing routines introduce a certain +/// 'latency' between the input and output, so that the samples input to +/// SoundTouch may not be immediately available in the output, and neither +/// the amount of outputtable samples may not immediately be in direct +/// relationship with the amount of previously input samples. +/// +/// - The tempo/pitch/rate control parameters can be altered during processing. +/// Please notice though that they aren't currently protected by semaphores, +/// so in multi-thread application external semaphore protection may be +/// required. +/// +/// - This class utilizes classes 'TDStretch' for tempo change (without modifying +/// pitch) and 'RateTransposer' for changing the playback rate (that is, both +/// tempo and pitch in the same ratio) of the sound. The third available control +/// 'pitch' (change pitch but maintain tempo) is produced by a combination of +/// combining the two other controls. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-06-13 12:29:53 -0700 (Wed, 13 Jun 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: SoundTouch.cpp 143 2012-06-13 19:29:53Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "SoundTouch.h" +#include "TDStretch.h" +#include "RateTransposer.h" +#include "cpu_detect.h" + +using namespace soundtouch; + +/// test if two floating point numbers are equal +#define TEST_FLOAT_EQUAL(a, b) (fabs(a - b) < 1e-10) + + +/// Print library version string for autoconf +extern "C" void soundtouch_ac_test() +{ + printf("SoundTouch Version: %s\n",SOUNDTOUCH_VERSION); +} + + +SoundTouch::SoundTouch() +{ + // Initialize rate transposer and tempo changer instances + + pRateTransposer = RateTransposer::newInstance(); + pTDStretch = TDStretch::newInstance(); + + setOutPipe(pTDStretch); + + rate = tempo = 0; + + virtualPitch = + virtualRate = + virtualTempo = 1.0; + + calcEffectiveRateAndTempo(); + + channels = 0; + bSrateSet = false; +} + + + +SoundTouch::~SoundTouch() +{ + delete pRateTransposer; + delete pTDStretch; +} + + + +/// Get SoundTouch library version string +const char *SoundTouch::getVersionString() +{ + static const char *_version = SOUNDTOUCH_VERSION; + + return _version; +} + + +/// Get SoundTouch library version Id +uint SoundTouch::getVersionId() +{ + return SOUNDTOUCH_VERSION_ID; +} + + +// Sets the number of channels, 1 = mono, 2 = stereo +void SoundTouch::setChannels(uint numChannels) +{ + if (numChannels != 1 && numChannels != 2) + { + ST_THROW_RT_ERROR("Illegal number of channels"); + } + channels = numChannels; + pRateTransposer->setChannels((int)numChannels); + pTDStretch->setChannels((int)numChannels); +} + + + +// Sets new rate control value. Normal rate = 1.0, smaller values +// represent slower rate, larger faster rates. +void SoundTouch::setRate(float newRate) +{ + virtualRate = newRate; + calcEffectiveRateAndTempo(); +} + + + +// Sets new rate control value as a difference in percents compared +// to the original rate (-50 .. +100 %) +void SoundTouch::setRateChange(float newRate) +{ + virtualRate = 1.0f + 0.01f * newRate; + calcEffectiveRateAndTempo(); +} + + + +// Sets new tempo control value. Normal tempo = 1.0, smaller values +// represent slower tempo, larger faster tempo. +void SoundTouch::setTempo(float newTempo) +{ + virtualTempo = newTempo; + calcEffectiveRateAndTempo(); +} + + + +// Sets new tempo control value as a difference in percents compared +// to the original tempo (-50 .. +100 %) +void SoundTouch::setTempoChange(float newTempo) +{ + virtualTempo = 1.0f + 0.01f * newTempo; + calcEffectiveRateAndTempo(); +} + + + +// Sets new pitch control value. Original pitch = 1.0, smaller values +// represent lower pitches, larger values higher pitch. +void SoundTouch::setPitch(float newPitch) +{ + virtualPitch = newPitch; + calcEffectiveRateAndTempo(); +} + + + +// Sets pitch change in octaves compared to the original pitch +// (-1.00 .. +1.00) +void SoundTouch::setPitchOctaves(float newPitch) +{ + virtualPitch = (float)exp(0.69314718056f * newPitch); + calcEffectiveRateAndTempo(); +} + + + +// Sets pitch change in semi-tones compared to the original pitch +// (-12 .. +12) +void SoundTouch::setPitchSemiTones(int newPitch) +{ + setPitchOctaves((float)newPitch / 12.0f); +} + + + +void SoundTouch::setPitchSemiTones(float newPitch) +{ + setPitchOctaves(newPitch / 12.0f); +} + + +// Calculates 'effective' rate and tempo values from the +// nominal control values. +void SoundTouch::calcEffectiveRateAndTempo() +{ + float oldTempo = tempo; + float oldRate = rate; + + tempo = virtualTempo / virtualPitch; + rate = virtualPitch * virtualRate; + + if (!TEST_FLOAT_EQUAL(rate,oldRate)) pRateTransposer->setRate(rate); + if (!TEST_FLOAT_EQUAL(tempo, oldTempo)) pTDStretch->setTempo(tempo); + +#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER + if (rate <= 1.0f) + { + if (output != pTDStretch) + { + FIFOSamplePipe *tempoOut; + + assert(output == pRateTransposer); + // move samples in the current output buffer to the output of pTDStretch + tempoOut = pTDStretch->getOutput(); + tempoOut->moveSamples(*output); + // move samples in pitch transposer's store buffer to tempo changer's input + pTDStretch->moveSamples(*pRateTransposer->getStore()); + + output = pTDStretch; + } + } + else +#endif + { + if (output != pRateTransposer) + { + FIFOSamplePipe *transOut; + + assert(output == pTDStretch); + // move samples in the current output buffer to the output of pRateTransposer + transOut = pRateTransposer->getOutput(); + transOut->moveSamples(*output); + // move samples in tempo changer's input to pitch transposer's input + pRateTransposer->moveSamples(*pTDStretch->getInput()); + + output = pRateTransposer; + } + } +} + + +// Sets sample rate. +void SoundTouch::setSampleRate(uint srate) +{ + bSrateSet = true; + // set sample rate, leave other tempo changer parameters as they are. + pTDStretch->setParameters((int)srate); +} + + +// Adds 'numSamples' pcs of samples from the 'samples' memory position into +// the input of the object. +void SoundTouch::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + if (bSrateSet == false) + { + ST_THROW_RT_ERROR("SoundTouch : Sample rate not defined"); + } + else if (channels == 0) + { + ST_THROW_RT_ERROR("SoundTouch : Number of channels not defined"); + } + + // Transpose the rate of the new samples if necessary + /* Bypass the nominal setting - can introduce a click in sound when tempo/pitch control crosses the nominal value... + if (rate == 1.0f) + { + // The rate value is same as the original, simply evaluate the tempo changer. + assert(output == pTDStretch); + if (pRateTransposer->isEmpty() == 0) + { + // yet flush the last samples in the pitch transposer buffer + // (may happen if 'rate' changes from a non-zero value to zero) + pTDStretch->moveSamples(*pRateTransposer); + } + pTDStretch->putSamples(samples, nSamples); + } + */ +#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER + else if (rate <= 1.0f) + { + // transpose the rate down, output the transposed sound to tempo changer buffer + assert(output == pTDStretch); + pRateTransposer->putSamples(samples, nSamples); + pTDStretch->moveSamples(*pRateTransposer); + } + else +#endif + { + // evaluate the tempo changer, then transpose the rate up, + assert(output == pRateTransposer); + pTDStretch->putSamples(samples, nSamples); + pRateTransposer->moveSamples(*pTDStretch); + } +} + + +// Flushes the last samples from the processing pipeline to the output. +// Clears also the internal processing buffers. +// +// Note: This function is meant for extracting the last samples of a sound +// stream. This function may introduce additional blank samples in the end +// of the sound stream, and thus it's not recommended to call this function +// in the middle of a sound stream. +void SoundTouch::flush() +{ + int i; + int nUnprocessed; + int nOut; + SAMPLETYPE buff[64*2]; // note: allocate 2*64 to cater 64 sample frames of stereo sound + + // check how many samples still await processing, and scale + // that by tempo & rate to get expected output sample count + nUnprocessed = numUnprocessedSamples(); + nUnprocessed = (int)((double)nUnprocessed / (tempo * rate) + 0.5); + + nOut = numSamples(); // ready samples currently in buffer ... + nOut += nUnprocessed; // ... and how many we expect there to be in the end + + memset(buff, 0, 64 * channels * sizeof(SAMPLETYPE)); + // "Push" the last active samples out from the processing pipeline by + // feeding blank samples into the processing pipeline until new, + // processed samples appear in the output (not however, more than + // 8ksamples in any case) + for (i = 0; i < 128; i ++) + { + putSamples(buff, 64); + if ((int)numSamples() >= nOut) + { + // Enough new samples have appeared into the output! + // As samples come from processing with bigger chunks, now truncate it + // back to maximum "nOut" samples to improve duration accuracy + adjustAmountOfSamples(nOut); + + // finish + break; + } + } + + // Clear working buffers + pRateTransposer->clear(); + pTDStretch->clearInput(); + // yet leave the 'tempoChanger' output intouched as that's where the + // flushed samples are! +} + + +// Changes a setting controlling the processing system behaviour. See the +// 'SETTING_...' defines for available setting ID's. +bool SoundTouch::setSetting(int settingId, int value) +{ + int sampleRate, sequenceMs, seekWindowMs, overlapMs; + + // read current tdstretch routine parameters + pTDStretch->getParameters(&sampleRate, &sequenceMs, &seekWindowMs, &overlapMs); + + switch (settingId) + { + case SETTING_USE_AA_FILTER : + // enables / disabless anti-alias filter + pRateTransposer->enableAAFilter((value != 0) ? true : false); + return true; + + case SETTING_AA_FILTER_LENGTH : + // sets anti-alias filter length + pRateTransposer->getAAFilter()->setLength(value); + return true; + + case SETTING_USE_QUICKSEEK : + // enables / disables tempo routine quick seeking algorithm + pTDStretch->enableQuickSeek((value != 0) ? true : false); + return true; + + case SETTING_SEQUENCE_MS: + // change time-stretch sequence duration parameter + pTDStretch->setParameters(sampleRate, value, seekWindowMs, overlapMs); + return true; + + case SETTING_SEEKWINDOW_MS: + // change time-stretch seek window length parameter + pTDStretch->setParameters(sampleRate, sequenceMs, value, overlapMs); + return true; + + case SETTING_OVERLAP_MS: + // change time-stretch overlap length parameter + pTDStretch->setParameters(sampleRate, sequenceMs, seekWindowMs, value); + return true; + + default : + return false; + } +} + + +// Reads a setting controlling the processing system behaviour. See the +// 'SETTING_...' defines for available setting ID's. +// +// Returns the setting value. +int SoundTouch::getSetting(int settingId) const +{ + int temp; + + switch (settingId) + { + case SETTING_USE_AA_FILTER : + return (uint)pRateTransposer->isAAFilterEnabled(); + + case SETTING_AA_FILTER_LENGTH : + return pRateTransposer->getAAFilter()->getLength(); + + case SETTING_USE_QUICKSEEK : + return (uint) pTDStretch->isQuickSeekEnabled(); + + case SETTING_SEQUENCE_MS: + pTDStretch->getParameters(NULL, &temp, NULL, NULL); + return temp; + + case SETTING_SEEKWINDOW_MS: + pTDStretch->getParameters(NULL, NULL, &temp, NULL); + return temp; + + case SETTING_OVERLAP_MS: + pTDStretch->getParameters(NULL, NULL, NULL, &temp); + return temp; + + case SETTING_NOMINAL_INPUT_SEQUENCE : + return pTDStretch->getInputSampleReq(); + + case SETTING_NOMINAL_OUTPUT_SEQUENCE : + return pTDStretch->getOutputBatchSize(); + + default : + return 0; + } +} + + +// Clears all the samples in the object's output and internal processing +// buffers. +void SoundTouch::clear() +{ + pRateTransposer->clear(); + pTDStretch->clear(); +} + + + +/// Returns number of samples currently unprocessed. +uint SoundTouch::numUnprocessedSamples() const +{ + FIFOSamplePipe * psp; + if (pTDStretch) + { + psp = pTDStretch->getInput(); + if (psp) + { + return psp->numSamples(); + } + } + return 0; +} diff --git a/media/libsoundtouch/src/SoundTouch.h b/media/libsoundtouch/src/SoundTouch.h new file mode 100644 index 00000000000..42319ad8521 --- /dev/null +++ b/media/libsoundtouch/src/SoundTouch.h @@ -0,0 +1,277 @@ +////////////////////////////////////////////////////////////////////////////// +/// +/// SoundTouch - main class for tempo/pitch/rate adjusting routines. +/// +/// Notes: +/// - Initialize the SoundTouch object instance by setting up the sound stream +/// parameters with functions 'setSampleRate' and 'setChannels', then set +/// desired tempo/pitch/rate settings with the corresponding functions. +/// +/// - The SoundTouch class behaves like a first-in-first-out pipeline: The +/// samples that are to be processed are fed into one of the pipe by calling +/// function 'putSamples', while the ready processed samples can be read +/// from the other end of the pipeline with function 'receiveSamples'. +/// +/// - The SoundTouch processing classes require certain sized 'batches' of +/// samples in order to process the sound. For this reason the classes buffer +/// incoming samples until there are enough of samples available for +/// processing, then they carry out the processing step and consequently +/// make the processed samples available for outputting. +/// +/// - For the above reason, the processing routines introduce a certain +/// 'latency' between the input and output, so that the samples input to +/// SoundTouch may not be immediately available in the output, and neither +/// the amount of outputtable samples may not immediately be in direct +/// relationship with the amount of previously input samples. +/// +/// - The tempo/pitch/rate control parameters can be altered during processing. +/// Please notice though that they aren't currently protected by semaphores, +/// so in multi-thread application external semaphore protection may be +/// required. +/// +/// - This class utilizes classes 'TDStretch' for tempo change (without modifying +/// pitch) and 'RateTransposer' for changing the playback rate (that is, both +/// tempo and pitch in the same ratio) of the sound. The third available control +/// 'pitch' (change pitch but maintain tempo) is produced by a combination of +/// combining the two other controls. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-04 12:47:28 -0700 (Wed, 04 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: SoundTouch.h 141 2012-04-04 19:47:28Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef SoundTouch_H +#define SoundTouch_H + +#include "FIFOSamplePipe.h" +#include "STTypes.h" + +namespace soundtouch +{ + +/// Soundtouch library version string +#define SOUNDTOUCH_VERSION "1.7.0" + +/// SoundTouch library version id +#define SOUNDTOUCH_VERSION_ID (10700) + +// +// Available setting IDs for the 'setSetting' & 'get_setting' functions: + +/// Enable/disable anti-alias filter in pitch transposer (0 = disable) +#define SETTING_USE_AA_FILTER 0 + +/// Pitch transposer anti-alias filter length (8 .. 128 taps, default = 32) +#define SETTING_AA_FILTER_LENGTH 1 + +/// Enable/disable quick seeking algorithm in tempo changer routine +/// (enabling quick seeking lowers CPU utilization but causes a minor sound +/// quality compromising) +#define SETTING_USE_QUICKSEEK 2 + +/// Time-stretch algorithm single processing sequence length in milliseconds. This determines +/// to how long sequences the original sound is chopped in the time-stretch algorithm. +/// See "STTypes.h" or README for more information. +#define SETTING_SEQUENCE_MS 3 + +/// Time-stretch algorithm seeking window length in milliseconds for algorithm that finds the +/// best possible overlapping location. This determines from how wide window the algorithm +/// may look for an optimal joining location when mixing the sound sequences back together. +/// See "STTypes.h" or README for more information. +#define SETTING_SEEKWINDOW_MS 4 + +/// Time-stretch algorithm overlap length in milliseconds. When the chopped sound sequences +/// are mixed back together, to form a continuous sound stream, this parameter defines over +/// how long period the two consecutive sequences are let to overlap each other. +/// See "STTypes.h" or README for more information. +#define SETTING_OVERLAP_MS 5 + + +/// Call "getSetting" with this ID to query nominal average processing sequence +/// size in samples. This value tells approcimate value how many input samples +/// SoundTouch needs to gather before it does DSP processing run for the sample batch. +/// +/// Notices: +/// - This is read-only parameter, i.e. setSetting ignores this parameter +/// - Returned value is approximate average value, exact processing batch +/// size may wary from time to time +/// - This parameter value is not constant but may change depending on +/// tempo/pitch/rate/samplerate settings. +#define SETTING_NOMINAL_INPUT_SEQUENCE 6 + + +/// Call "getSetting" with this ID to query nominal average processing output +/// size in samples. This value tells approcimate value how many output samples +/// SoundTouch outputs once it does DSP processing run for a batch of input samples. +/// +/// Notices: +/// - This is read-only parameter, i.e. setSetting ignores this parameter +/// - Returned value is approximate average value, exact processing batch +/// size may wary from time to time +/// - This parameter value is not constant but may change depending on +/// tempo/pitch/rate/samplerate settings. +#define SETTING_NOMINAL_OUTPUT_SEQUENCE 7 + +class EXPORT SoundTouch : public FIFOProcessor +{ +private: + /// Rate transposer class instance + class RateTransposer *pRateTransposer; + + /// Time-stretch class instance + class TDStretch *pTDStretch; + + /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. + float virtualRate; + + /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. + float virtualTempo; + + /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. + float virtualPitch; + + /// Flag: Has sample rate been set? + bool bSrateSet; + + /// Calculates effective rate & tempo valuescfrom 'virtualRate', 'virtualTempo' and + /// 'virtualPitch' parameters. + void calcEffectiveRateAndTempo(); + +protected : + /// Number of channels + uint channels; + + /// Effective 'rate' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch' + float rate; + + /// Effective 'tempo' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch' + float tempo; + +public: + SoundTouch(); + virtual ~SoundTouch(); + + /// Get SoundTouch library version string + static const char *getVersionString(); + + /// Get SoundTouch library version Id + static uint getVersionId(); + + /// Sets new rate control value. Normal rate = 1.0, smaller values + /// represent slower rate, larger faster rates. + void setRate(float newRate); + + /// Sets new tempo control value. Normal tempo = 1.0, smaller values + /// represent slower tempo, larger faster tempo. + void setTempo(float newTempo); + + /// Sets new rate control value as a difference in percents compared + /// to the original rate (-50 .. +100 %) + void setRateChange(float newRate); + + /// Sets new tempo control value as a difference in percents compared + /// to the original tempo (-50 .. +100 %) + void setTempoChange(float newTempo); + + /// Sets new pitch control value. Original pitch = 1.0, smaller values + /// represent lower pitches, larger values higher pitch. + void setPitch(float newPitch); + + /// Sets pitch change in octaves compared to the original pitch + /// (-1.00 .. +1.00) + void setPitchOctaves(float newPitch); + + /// Sets pitch change in semi-tones compared to the original pitch + /// (-12 .. +12) + void setPitchSemiTones(int newPitch); + void setPitchSemiTones(float newPitch); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(uint numChannels); + + /// Sets sample rate. + void setSampleRate(uint srate); + + /// Flushes the last samples from the processing pipeline to the output. + /// Clears also the internal processing buffers. + // + /// Note: This function is meant for extracting the last samples of a sound + /// stream. This function may introduce additional blank samples in the end + /// of the sound stream, and thus it's not recommended to call this function + /// in the middle of a sound stream. + void flush(); + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position into + /// the input of the object. Notice that sample rate _has_to_ be set before + /// calling this function, otherwise throws a runtime_error exception. + virtual void putSamples( + const SAMPLETYPE *samples, ///< Pointer to sample buffer. + uint numSamples ///< Number of samples in buffer. Notice + ///< that in case of stereo-sound a single sample + ///< contains data for both channels. + ); + + /// Clears all the samples in the object's output and internal processing + /// buffers. + virtual void clear(); + + /// Changes a setting controlling the processing system behaviour. See the + /// 'SETTING_...' defines for available setting ID's. + /// + /// \return 'true' if the setting was succesfully changed + bool setSetting(int settingId, ///< Setting ID number. see SETTING_... defines. + int value ///< New setting value. + ); + + /// Reads a setting controlling the processing system behaviour. See the + /// 'SETTING_...' defines for available setting ID's. + /// + /// \return the setting value. + int getSetting(int settingId ///< Setting ID number, see SETTING_... defines. + ) const; + + /// Returns number of samples currently unprocessed. + virtual uint numUnprocessedSamples() const; + + + /// Other handy functions that are implemented in the ancestor classes (see + /// classes 'FIFOProcessor' and 'FIFOSamplePipe') + /// + /// - receiveSamples() : Use this function to receive 'ready' processed samples from SoundTouch. + /// - numSamples() : Get number of 'ready' samples that can be received with + /// function 'receiveSamples()' + /// - isEmpty() : Returns nonzero if there aren't any 'ready' samples. + /// - clear() : Clears all samples from ready/processing buffers. +}; + +} +#endif diff --git a/media/libsoundtouch/src/TDStretch.cpp b/media/libsoundtouch/src/TDStretch.cpp new file mode 100644 index 00000000000..bec03ae2d6b --- /dev/null +++ b/media/libsoundtouch/src/TDStretch.cpp @@ -0,0 +1,808 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo +/// while maintaining the original pitch by using a time domain WSOLA-like +/// method with several performance-increasing tweaks. +/// +/// Note : MMX optimized functions reside in a separate, platform-specific +/// file, e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 1.12 $ +// +// $Id: TDStretch.cpp 137 2012-04-01 19:49:30Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "STTypes.h" +#include "cpu_detect.h" +#include "TDStretch.h" + +#include + +using namespace soundtouch; + +#define max(x, y) (((x) > (y)) ? (x) : (y)) + + +/***************************************************************************** + * + * Constant definitions + * + *****************************************************************************/ + +// Table for the hierarchical mixing position seeking algorithm +static const short _scanOffsets[5][24]={ + { 124, 186, 248, 310, 372, 434, 496, 558, 620, 682, 744, 806, + 868, 930, 992, 1054, 1116, 1178, 1240, 1302, 1364, 1426, 1488, 0}, + {-100, -75, -50, -25, 25, 50, 75, 100, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { -20, -15, -10, -5, 5, 10, 15, 20, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { -4, -3, -2, -1, 1, 2, 3, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 121, 114, 97, 114, 98, 105, 108, 32, 104, 99, 117, 111, + 116, 100, 110, 117, 111, 115, 0, 0, 0, 0, 0, 0}}; + +/***************************************************************************** + * + * Implementation of the class 'TDStretch' + * + *****************************************************************************/ + + +TDStretch::TDStretch() : FIFOProcessor(&outputBuffer) +{ + bQuickSeek = false; + channels = 2; + + pMidBuffer = NULL; + pMidBufferUnaligned = NULL; + overlapLength = 0; + + bAutoSeqSetting = true; + bAutoSeekSetting = true; + +// outDebt = 0; + skipFract = 0; + + tempo = 1.0f; + setParameters(44100, DEFAULT_SEQUENCE_MS, DEFAULT_SEEKWINDOW_MS, DEFAULT_OVERLAP_MS); + setTempo(1.0f); + + clear(); +} + + + +TDStretch::~TDStretch() +{ + delete[] pMidBufferUnaligned; +} + + + +// Sets routine control parameters. These control are certain time constants +// defining how the sound is stretched to the desired duration. +// +// 'sampleRate' = sample rate of the sound +// 'sequenceMS' = one processing sequence length in milliseconds (default = 82 ms) +// 'seekwindowMS' = seeking window length for scanning the best overlapping +// position (default = 28 ms) +// 'overlapMS' = overlapping length (default = 12 ms) + +void TDStretch::setParameters(int aSampleRate, int aSequenceMS, + int aSeekWindowMS, int aOverlapMS) +{ + // accept only positive parameter values - if zero or negative, use old values instead + if (aSampleRate > 0) this->sampleRate = aSampleRate; + if (aOverlapMS > 0) this->overlapMs = aOverlapMS; + + if (aSequenceMS > 0) + { + this->sequenceMs = aSequenceMS; + bAutoSeqSetting = false; + } + else if (aSequenceMS == 0) + { + // if zero, use automatic setting + bAutoSeqSetting = true; + } + + if (aSeekWindowMS > 0) + { + this->seekWindowMs = aSeekWindowMS; + bAutoSeekSetting = false; + } + else if (aSeekWindowMS == 0) + { + // if zero, use automatic setting + bAutoSeekSetting = true; + } + + calcSeqParameters(); + + calculateOverlapLength(overlapMs); + + // set tempo to recalculate 'sampleReq' + setTempo(tempo); + +} + + + +/// Get routine control parameters, see setParameters() function. +/// Any of the parameters to this function can be NULL, in such case corresponding parameter +/// value isn't returned. +void TDStretch::getParameters(int *pSampleRate, int *pSequenceMs, int *pSeekWindowMs, int *pOverlapMs) const +{ + if (pSampleRate) + { + *pSampleRate = sampleRate; + } + + if (pSequenceMs) + { + *pSequenceMs = (bAutoSeqSetting) ? (USE_AUTO_SEQUENCE_LEN) : sequenceMs; + } + + if (pSeekWindowMs) + { + *pSeekWindowMs = (bAutoSeekSetting) ? (USE_AUTO_SEEKWINDOW_LEN) : seekWindowMs; + } + + if (pOverlapMs) + { + *pOverlapMs = overlapMs; + } +} + + +// Overlaps samples in 'midBuffer' with the samples in 'pInput' +void TDStretch::overlapMono(SAMPLETYPE *pOutput, const SAMPLETYPE *pInput) const +{ + int i; + SAMPLETYPE m1, m2; + + m1 = (SAMPLETYPE)0; + m2 = (SAMPLETYPE)overlapLength; + + for (i = 0; i < overlapLength ; i ++) + { + pOutput[i] = (pInput[i] * m1 + pMidBuffer[i] * m2 ) / overlapLength; + m1 += 1; + m2 -= 1; + } +} + + + +void TDStretch::clearMidBuffer() +{ + memset(pMidBuffer, 0, 2 * sizeof(SAMPLETYPE) * overlapLength); +} + + +void TDStretch::clearInput() +{ + inputBuffer.clear(); + clearMidBuffer(); +} + + +// Clears the sample buffers +void TDStretch::clear() +{ + outputBuffer.clear(); + clearInput(); +} + + + +// Enables/disables the quick position seeking algorithm. Zero to disable, nonzero +// to enable +void TDStretch::enableQuickSeek(bool enable) +{ + bQuickSeek = enable; +} + + +// Returns nonzero if the quick seeking algorithm is enabled. +bool TDStretch::isQuickSeekEnabled() const +{ + return bQuickSeek; +} + + +// Seeks for the optimal overlap-mixing position. +int TDStretch::seekBestOverlapPosition(const SAMPLETYPE *refPos) +{ + if (bQuickSeek) + { + return seekBestOverlapPositionQuick(refPos); + } + else + { + return seekBestOverlapPositionFull(refPos); + } +} + + +// Overlaps samples in 'midBuffer' with the samples in 'pInputBuffer' at position +// of 'ovlPos'. +inline void TDStretch::overlap(SAMPLETYPE *pOutput, const SAMPLETYPE *pInput, uint ovlPos) const +{ + if (channels == 2) + { + // stereo sound + overlapStereo(pOutput, pInput + 2 * ovlPos); + } else { + // mono sound. + overlapMono(pOutput, pInput + ovlPos); + } +} + + + +// Seeks for the optimal overlap-mixing position. The 'stereo' version of the +// routine +// +// The best position is determined as the position where the two overlapped +// sample sequences are 'most alike', in terms of the highest cross-correlation +// value over the overlapping period +int TDStretch::seekBestOverlapPositionFull(const SAMPLETYPE *refPos) +{ + int bestOffs; + double bestCorr, corr; + int i; + + bestCorr = FLT_MIN; + bestOffs = 0; + + // Scans for the best correlation value by testing each possible position + // over the permitted range. + for (i = 0; i < seekLength; i ++) + { + // Calculates correlation value for the mixing position corresponding + // to 'i' + corr = calcCrossCorr(refPos + channels * i, pMidBuffer); + // heuristic rule to slightly favour values close to mid of the range + double tmp = (double)(2 * i - seekLength) / (double)seekLength; + corr = ((corr + 0.1) * (1.0 - 0.25 * tmp * tmp)); + + // Checks for the highest correlation value + if (corr > bestCorr) + { + bestCorr = corr; + bestOffs = i; + } + } + // clear cross correlation routine state if necessary (is so e.g. in MMX routines). + clearCrossCorrState(); + + return bestOffs; +} + + +// Seeks for the optimal overlap-mixing position. The 'stereo' version of the +// routine +// +// The best position is determined as the position where the two overlapped +// sample sequences are 'most alike', in terms of the highest cross-correlation +// value over the overlapping period +int TDStretch::seekBestOverlapPositionQuick(const SAMPLETYPE *refPos) +{ + int j; + int bestOffs; + double bestCorr, corr; + int scanCount, corrOffset, tempOffset; + + bestCorr = FLT_MIN; + bestOffs = _scanOffsets[0][0]; + corrOffset = 0; + tempOffset = 0; + + // Scans for the best correlation value using four-pass hierarchical search. + // + // The look-up table 'scans' has hierarchical position adjusting steps. + // In first pass the routine searhes for the highest correlation with + // relatively coarse steps, then rescans the neighbourhood of the highest + // correlation with better resolution and so on. + for (scanCount = 0;scanCount < 4; scanCount ++) + { + j = 0; + while (_scanOffsets[scanCount][j]) + { + tempOffset = corrOffset + _scanOffsets[scanCount][j]; + if (tempOffset >= seekLength) break; + + // Calculates correlation value for the mixing position corresponding + // to 'tempOffset' + corr = (double)calcCrossCorr(refPos + channels * tempOffset, pMidBuffer); + // heuristic rule to slightly favour values close to mid of the range + double tmp = (double)(2 * tempOffset - seekLength) / seekLength; + corr = ((corr + 0.1) * (1.0 - 0.25 * tmp * tmp)); + + // Checks for the highest correlation value + if (corr > bestCorr) + { + bestCorr = corr; + bestOffs = tempOffset; + } + j ++; + } + corrOffset = bestOffs; + } + // clear cross correlation routine state if necessary (is so e.g. in MMX routines). + clearCrossCorrState(); + + return bestOffs; +} + + + +/// clear cross correlation routine state if necessary +void TDStretch::clearCrossCorrState() +{ + // default implementation is empty. +} + + +/// Calculates processing sequence length according to tempo setting +void TDStretch::calcSeqParameters() +{ + // Adjust tempo param according to tempo, so that variating processing sequence length is used + // at varius tempo settings, between the given low...top limits + #define AUTOSEQ_TEMPO_LOW 0.5 // auto setting low tempo range (-50%) + #define AUTOSEQ_TEMPO_TOP 2.0 // auto setting top tempo range (+100%) + + // sequence-ms setting values at above low & top tempo + #define AUTOSEQ_AT_MIN 125.0 + #define AUTOSEQ_AT_MAX 50.0 + #define AUTOSEQ_K ((AUTOSEQ_AT_MAX - AUTOSEQ_AT_MIN) / (AUTOSEQ_TEMPO_TOP - AUTOSEQ_TEMPO_LOW)) + #define AUTOSEQ_C (AUTOSEQ_AT_MIN - (AUTOSEQ_K) * (AUTOSEQ_TEMPO_LOW)) + + // seek-window-ms setting values at above low & top tempo + #define AUTOSEEK_AT_MIN 25.0 + #define AUTOSEEK_AT_MAX 15.0 + #define AUTOSEEK_K ((AUTOSEEK_AT_MAX - AUTOSEEK_AT_MIN) / (AUTOSEQ_TEMPO_TOP - AUTOSEQ_TEMPO_LOW)) + #define AUTOSEEK_C (AUTOSEEK_AT_MIN - (AUTOSEEK_K) * (AUTOSEQ_TEMPO_LOW)) + + #define CHECK_LIMITS(x, mi, ma) (((x) < (mi)) ? (mi) : (((x) > (ma)) ? (ma) : (x))) + + double seq, seek; + + if (bAutoSeqSetting) + { + seq = AUTOSEQ_C + AUTOSEQ_K * tempo; + seq = CHECK_LIMITS(seq, AUTOSEQ_AT_MAX, AUTOSEQ_AT_MIN); + sequenceMs = (int)(seq + 0.5); + } + + if (bAutoSeekSetting) + { + seek = AUTOSEEK_C + AUTOSEEK_K * tempo; + seek = CHECK_LIMITS(seek, AUTOSEEK_AT_MAX, AUTOSEEK_AT_MIN); + seekWindowMs = (int)(seek + 0.5); + } + + // Update seek window lengths + seekWindowLength = (sampleRate * sequenceMs) / 1000; + if (seekWindowLength < 2 * overlapLength) + { + seekWindowLength = 2 * overlapLength; + } + seekLength = (sampleRate * seekWindowMs) / 1000; +} + + + +// Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower +// tempo, larger faster tempo. +void TDStretch::setTempo(float newTempo) +{ + int intskip; + + tempo = newTempo; + + // Calculate new sequence duration + calcSeqParameters(); + + // Calculate ideal skip length (according to tempo value) + nominalSkip = tempo * (seekWindowLength - overlapLength); + intskip = (int)(nominalSkip + 0.5f); + + // Calculate how many samples are needed in the 'inputBuffer' to + // process another batch of samples + //sampleReq = max(intskip + overlapLength, seekWindowLength) + seekLength / 2; + sampleReq = max(intskip + overlapLength, seekWindowLength) + seekLength; +} + + + +// Sets the number of channels, 1 = mono, 2 = stereo +void TDStretch::setChannels(int numChannels) +{ + assert(numChannels > 0); + if (channels == numChannels) return; + assert(numChannels == 1 || numChannels == 2); + + channels = numChannels; + inputBuffer.setChannels(channels); + outputBuffer.setChannels(channels); +} + + +// nominal tempo, no need for processing, just pass the samples through +// to outputBuffer +/* +void TDStretch::processNominalTempo() +{ + assert(tempo == 1.0f); + + if (bMidBufferDirty) + { + // If there are samples in pMidBuffer waiting for overlapping, + // do a single sliding overlapping with them in order to prevent a + // clicking distortion in the output sound + if (inputBuffer.numSamples() < overlapLength) + { + // wait until we've got overlapLength input samples + return; + } + // Mix the samples in the beginning of 'inputBuffer' with the + // samples in 'midBuffer' using sliding overlapping + overlap(outputBuffer.ptrEnd(overlapLength), inputBuffer.ptrBegin(), 0); + outputBuffer.putSamples(overlapLength); + inputBuffer.receiveSamples(overlapLength); + clearMidBuffer(); + // now we've caught the nominal sample flow and may switch to + // bypass mode + } + + // Simply bypass samples from input to output + outputBuffer.moveSamples(inputBuffer); +} +*/ + +#include + +// Processes as many processing frames of the samples 'inputBuffer', store +// the result into 'outputBuffer' +void TDStretch::processSamples() +{ + int ovlSkip, offset; + int temp; + + /* Removed this small optimization - can introduce a click to sound when tempo setting + crosses the nominal value + if (tempo == 1.0f) + { + // tempo not changed from the original, so bypass the processing + processNominalTempo(); + return; + } + */ + + // Process samples as long as there are enough samples in 'inputBuffer' + // to form a processing frame. + while ((int)inputBuffer.numSamples() >= sampleReq) + { + // If tempo differs from the normal ('SCALE'), scan for the best overlapping + // position + offset = seekBestOverlapPosition(inputBuffer.ptrBegin()); + + // Mix the samples in the 'inputBuffer' at position of 'offset' with the + // samples in 'midBuffer' using sliding overlapping + // ... first partially overlap with the end of the previous sequence + // (that's in 'midBuffer') + overlap(outputBuffer.ptrEnd((uint)overlapLength), inputBuffer.ptrBegin(), (uint)offset); + outputBuffer.putSamples((uint)overlapLength); + + // ... then copy sequence samples from 'inputBuffer' to output: + + // length of sequence + temp = (seekWindowLength - 2 * overlapLength); + + // crosscheck that we don't have buffer overflow... + if ((int)inputBuffer.numSamples() < (offset + temp + overlapLength * 2)) + { + continue; // just in case, shouldn't really happen + } + + outputBuffer.putSamples(inputBuffer.ptrBegin() + channels * (offset + overlapLength), (uint)temp); + + // Copies the end of the current sequence from 'inputBuffer' to + // 'midBuffer' for being mixed with the beginning of the next + // processing sequence and so on + assert((offset + temp + overlapLength * 2) <= (int)inputBuffer.numSamples()); + memcpy(pMidBuffer, inputBuffer.ptrBegin() + channels * (offset + temp + overlapLength), + channels * sizeof(SAMPLETYPE) * overlapLength); + + // Remove the processed samples from the input buffer. Update + // the difference between integer & nominal skip step to 'skipFract' + // in order to prevent the error from accumulating over time. + skipFract += nominalSkip; // real skip size + ovlSkip = (int)skipFract; // rounded to integer skip + skipFract -= ovlSkip; // maintain the fraction part, i.e. real vs. integer skip + inputBuffer.receiveSamples((uint)ovlSkip); + } +} + + +// Adds 'numsamples' pcs of samples from the 'samples' memory position into +// the input of the object. +void TDStretch::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + // Add the samples into the input buffer + inputBuffer.putSamples(samples, nSamples); + // Process the samples in input buffer + processSamples(); +} + + + +/// Set new overlap length parameter & reallocate RefMidBuffer if necessary. +void TDStretch::acceptNewOverlapLength(int newOverlapLength) +{ + int prevOvl; + + assert(newOverlapLength >= 0); + prevOvl = overlapLength; + overlapLength = newOverlapLength; + + if (overlapLength > prevOvl) + { + delete[] pMidBufferUnaligned; + + pMidBufferUnaligned = new SAMPLETYPE[overlapLength * 2 + 16 / sizeof(SAMPLETYPE)]; + // ensure that 'pMidBuffer' is aligned to 16 byte boundary for efficiency + pMidBuffer = (SAMPLETYPE *)((((ulong)pMidBufferUnaligned) + 15) & (ulong)-16); + + clearMidBuffer(); + } +} + + +// Operator 'new' is overloaded so that it automatically creates a suitable instance +// depending on if we've a MMX/SSE/etc-capable CPU available or not. +void * TDStretch::operator new(size_t s) +{ + // Notice! don't use "new TDStretch" directly, use "newInstance" to create a new instance instead! + ST_THROW_RT_ERROR("Error in TDStretch::new: Don't use 'new TDStretch' directly, use 'newInstance' member instead!"); + return newInstance(); +} + + +TDStretch * TDStretch::newInstance() +{ + uint uExtensions; + + uExtensions = detectCPUextensions(); + + // Check if MMX/SSE instruction set extensions supported by CPU + +#ifdef SOUNDTOUCH_ALLOW_MMX + // MMX routines available only with integer sample types + if (uExtensions & SUPPORT_MMX) + { + return ::new TDStretchMMX; + } + else +#endif // SOUNDTOUCH_ALLOW_MMX + + +#ifdef SOUNDTOUCH_ALLOW_SSE + if (uExtensions & SUPPORT_SSE) + { + // SSE support + return ::new TDStretchSSE; + } + else +#endif // SOUNDTOUCH_ALLOW_SSE + + { + // ISA optimizations not supported, use plain C version + return ::new TDStretch; + } +} + + +////////////////////////////////////////////////////////////////////////////// +// +// Integer arithmetics specific algorithm implementations. +// +////////////////////////////////////////////////////////////////////////////// + +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + +// Overlaps samples in 'midBuffer' with the samples in 'input'. The 'Stereo' +// version of the routine. +void TDStretch::overlapStereo(short *poutput, const short *input) const +{ + int i; + short temp; + int cnt2; + + for (i = 0; i < overlapLength ; i ++) + { + temp = (short)(overlapLength - i); + cnt2 = 2 * i; + poutput[cnt2] = (input[cnt2] * i + pMidBuffer[cnt2] * temp ) / overlapLength; + poutput[cnt2 + 1] = (input[cnt2 + 1] * i + pMidBuffer[cnt2 + 1] * temp ) / overlapLength; + } +} + +// Calculates the x having the closest 2^x value for the given value +static int _getClosest2Power(double value) +{ + return (int)(log(value) / log(2.0) + 0.5); +} + + +/// Calculates overlap period length in samples. +/// Integer version rounds overlap length to closest power of 2 +/// for a divide scaling operation. +void TDStretch::calculateOverlapLength(int aoverlapMs) +{ + int newOvl; + + assert(aoverlapMs >= 0); + + // calculate overlap length so that it's power of 2 - thus it's easy to do + // integer division by right-shifting. Term "-1" at end is to account for + // the extra most significatnt bit left unused in result by signed multiplication + overlapDividerBits = _getClosest2Power((sampleRate * aoverlapMs) / 1000.0) - 1; + if (overlapDividerBits > 9) overlapDividerBits = 9; + if (overlapDividerBits < 3) overlapDividerBits = 3; + newOvl = (int)pow(2.0, (int)overlapDividerBits + 1); // +1 => account for -1 above + + acceptNewOverlapLength(newOvl); + + // calculate sloping divider so that crosscorrelation operation won't + // overflow 32-bit register. Max. sum of the crosscorrelation sum without + // divider would be 2^30*(N^3-N)/3, where N = overlap length + slopingDivider = (newOvl * newOvl - 1) / 3; +} + + +double TDStretch::calcCrossCorr(const short *mixingPos, const short *compare) const +{ + long corr; + long norm; + int i; + + corr = norm = 0; + // Same routine for stereo and mono. For stereo, unroll loop for better + // efficiency and gives slightly better resolution against rounding. + // For mono it same routine, just unrolls loop by factor of 4 + for (i = 0; i < channels * overlapLength; i += 4) + { + corr += (mixingPos[i] * compare[i] + + mixingPos[i + 1] * compare[i + 1] + + mixingPos[i + 2] * compare[i + 2] + + mixingPos[i + 3] * compare[i + 3]) >> overlapDividerBits; + norm += (mixingPos[i] * mixingPos[i] + + mixingPos[i + 1] * mixingPos[i + 1] + + mixingPos[i + 2] * mixingPos[i + 2] + + mixingPos[i + 3] * mixingPos[i + 3]) >> overlapDividerBits; + } + + // Normalize result by dividing by sqrt(norm) - this step is easiest + // done using floating point operation + if (norm == 0) norm = 1; // to avoid div by zero + return (double)corr / sqrt((double)norm); +} + +#endif // SOUNDTOUCH_INTEGER_SAMPLES + +////////////////////////////////////////////////////////////////////////////// +// +// Floating point arithmetics specific algorithm implementations. +// + +#ifdef SOUNDTOUCH_FLOAT_SAMPLES + +// Overlaps samples in 'midBuffer' with the samples in 'pInput' +void TDStretch::overlapStereo(float *pOutput, const float *pInput) const +{ + int i; + float fScale; + float f1; + float f2; + + fScale = 1.0f / (float)overlapLength; + + f1 = 0; + f2 = 1.0f; + + for (i = 0; i < 2 * (int)overlapLength ; i += 2) + { + pOutput[i + 0] = pInput[i + 0] * f1 + pMidBuffer[i + 0] * f2; + pOutput[i + 1] = pInput[i + 1] * f1 + pMidBuffer[i + 1] * f2; + + f1 += fScale; + f2 -= fScale; + } +} + + +/// Calculates overlapInMsec period length in samples. +void TDStretch::calculateOverlapLength(int overlapInMsec) +{ + int newOvl; + + assert(overlapInMsec >= 0); + newOvl = (sampleRate * overlapInMsec) / 1000; + if (newOvl < 16) newOvl = 16; + + // must be divisible by 8 + newOvl -= newOvl % 8; + + acceptNewOverlapLength(newOvl); +} + + +double TDStretch::calcCrossCorr(const float *mixingPos, const float *compare) const +{ + double corr; + double norm; + int i; + + corr = norm = 0; + // Same routine for stereo and mono. For Stereo, unroll by factor of 2. + // For mono it's same routine yet unrollsd by factor of 4. + for (i = 0; i < channels * overlapLength; i += 4) + { + corr += mixingPos[i] * compare[i] + + mixingPos[i + 1] * compare[i + 1]; + + norm += mixingPos[i] * mixingPos[i] + + mixingPos[i + 1] * mixingPos[i + 1]; + + // unroll the loop for better CPU efficiency: + corr += mixingPos[i + 2] * compare[i + 2] + + mixingPos[i + 3] * compare[i + 3]; + + norm += mixingPos[i + 2] * mixingPos[i + 2] + + mixingPos[i + 3] * mixingPos[i + 3]; + } + + if (norm < 1e-9) norm = 1.0; // to avoid div by zero + return corr / sqrt(norm); +} + +#endif // SOUNDTOUCH_FLOAT_SAMPLES diff --git a/media/libsoundtouch/src/TDStretch.h b/media/libsoundtouch/src/TDStretch.h new file mode 100644 index 00000000000..fcd0767e1f9 --- /dev/null +++ b/media/libsoundtouch/src/TDStretch.h @@ -0,0 +1,268 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo +/// while maintaining the original pitch by using a time domain WSOLA-like method +/// with several performance-increasing tweaks. +/// +/// Note : MMX/SSE optimized functions reside in separate, platform-specific files +/// 'mmx_optimized.cpp' and 'sse_optimized.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: TDStretch.h 137 2012-04-01 19:49:30Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef TDStretch_H +#define TDStretch_H + +#include +#include "STTypes.h" +#include "RateTransposer.h" +#include "FIFOSamplePipe.h" + +namespace soundtouch +{ + +/// Default values for sound processing parameters: +/// Notice that the default parameters are tuned for contemporary popular music +/// processing. For speech processing applications these parameters suit better: +/// #define DEFAULT_SEQUENCE_MS 40 +/// #define DEFAULT_SEEKWINDOW_MS 15 +/// #define DEFAULT_OVERLAP_MS 8 +/// + +/// Default length of a single processing sequence, in milliseconds. This determines to how +/// long sequences the original sound is chopped in the time-stretch algorithm. +/// +/// The larger this value is, the lesser sequences are used in processing. In principle +/// a bigger value sounds better when slowing down tempo, but worse when increasing tempo +/// and vice versa. +/// +/// Increasing this value reduces computational burden & vice versa. +//#define DEFAULT_SEQUENCE_MS 40 +#define DEFAULT_SEQUENCE_MS USE_AUTO_SEQUENCE_LEN + +/// Giving this value for the sequence length sets automatic parameter value +/// according to tempo setting (recommended) +#define USE_AUTO_SEQUENCE_LEN 0 + +/// Seeking window default length in milliseconds for algorithm that finds the best possible +/// overlapping location. This determines from how wide window the algorithm may look for an +/// optimal joining location when mixing the sound sequences back together. +/// +/// The bigger this window setting is, the higher the possibility to find a better mixing +/// position will become, but at the same time large values may cause a "drifting" artifact +/// because consequent sequences will be taken at more uneven intervals. +/// +/// If there's a disturbing artifact that sounds as if a constant frequency was drifting +/// around, try reducing this setting. +/// +/// Increasing this value increases computational burden & vice versa. +//#define DEFAULT_SEEKWINDOW_MS 15 +#define DEFAULT_SEEKWINDOW_MS USE_AUTO_SEEKWINDOW_LEN + +/// Giving this value for the seek window length sets automatic parameter value +/// according to tempo setting (recommended) +#define USE_AUTO_SEEKWINDOW_LEN 0 + +/// Overlap length in milliseconds. When the chopped sound sequences are mixed back together, +/// to form a continuous sound stream, this parameter defines over how long period the two +/// consecutive sequences are let to overlap each other. +/// +/// This shouldn't be that critical parameter. If you reduce the DEFAULT_SEQUENCE_MS setting +/// by a large amount, you might wish to try a smaller value on this. +/// +/// Increasing this value increases computational burden & vice versa. +#define DEFAULT_OVERLAP_MS 8 + + +/// Class that does the time-stretch (tempo change) effect for the processed +/// sound. +class TDStretch : public FIFOProcessor +{ +protected: + int channels; + int sampleReq; + float tempo; + + SAMPLETYPE *pMidBuffer; + SAMPLETYPE *pMidBufferUnaligned; + int overlapLength; + int seekLength; + int seekWindowLength; + int overlapDividerBits; + int slopingDivider; + float nominalSkip; + float skipFract; + FIFOSampleBuffer outputBuffer; + FIFOSampleBuffer inputBuffer; + bool bQuickSeek; + + int sampleRate; + int sequenceMs; + int seekWindowMs; + int overlapMs; + bool bAutoSeqSetting; + bool bAutoSeekSetting; + + void acceptNewOverlapLength(int newOverlapLength); + + virtual void clearCrossCorrState(); + void calculateOverlapLength(int overlapMs); + + virtual double calcCrossCorr(const SAMPLETYPE *mixingPos, const SAMPLETYPE *compare) const; + + virtual int seekBestOverlapPositionFull(const SAMPLETYPE *refPos); + virtual int seekBestOverlapPositionQuick(const SAMPLETYPE *refPos); + int seekBestOverlapPosition(const SAMPLETYPE *refPos); + + virtual void overlapStereo(SAMPLETYPE *output, const SAMPLETYPE *input) const; + virtual void overlapMono(SAMPLETYPE *output, const SAMPLETYPE *input) const; + + void clearMidBuffer(); + void overlap(SAMPLETYPE *output, const SAMPLETYPE *input, uint ovlPos) const; + + void calcSeqParameters(); + + /// Changes the tempo of the given sound samples. + /// Returns amount of samples returned in the "output" buffer. + /// The maximum amount of samples that can be returned at a time is set by + /// the 'set_returnBuffer_size' function. + void processSamples(); + +public: + TDStretch(); + virtual ~TDStretch(); + + /// Operator 'new' is overloaded so that it automatically creates a suitable instance + /// depending on if we've a MMX/SSE/etc-capable CPU available or not. + static void *operator new(size_t s); + + /// Use this function instead of "new" operator to create a new instance of this class. + /// This function automatically chooses a correct feature set depending on if the CPU + /// supports MMX/SSE/etc extensions. + static TDStretch *newInstance(); + + /// Returns the output buffer object + FIFOSamplePipe *getOutput() { return &outputBuffer; }; + + /// Returns the input buffer object + FIFOSamplePipe *getInput() { return &inputBuffer; }; + + /// Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower + /// tempo, larger faster tempo. + void setTempo(float newTempo); + + /// Returns nonzero if there aren't any samples available for outputting. + virtual void clear(); + + /// Clears the input buffer + void clearInput(); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(int numChannels); + + /// Enables/disables the quick position seeking algorithm. Zero to disable, + /// nonzero to enable + void enableQuickSeek(bool enable); + + /// Returns nonzero if the quick seeking algorithm is enabled. + bool isQuickSeekEnabled() const; + + /// Sets routine control parameters. These control are certain time constants + /// defining how the sound is stretched to the desired duration. + // + /// 'sampleRate' = sample rate of the sound + /// 'sequenceMS' = one processing sequence length in milliseconds + /// 'seekwindowMS' = seeking window length for scanning the best overlapping + /// position + /// 'overlapMS' = overlapping length + void setParameters(int sampleRate, ///< Samplerate of sound being processed (Hz) + int sequenceMS = -1, ///< Single processing sequence length (ms) + int seekwindowMS = -1, ///< Offset seeking window length (ms) + int overlapMS = -1 ///< Sequence overlapping length (ms) + ); + + /// Get routine control parameters, see setParameters() function. + /// Any of the parameters to this function can be NULL, in such case corresponding parameter + /// value isn't returned. + void getParameters(int *pSampleRate, int *pSequenceMs, int *pSeekWindowMs, int *pOverlapMs) const; + + /// Adds 'numsamples' pcs of samples from the 'samples' memory position into + /// the input of the object. + virtual void putSamples( + const SAMPLETYPE *samples, ///< Input sample data + uint numSamples ///< Number of samples in 'samples' so that one sample + ///< contains both channels if stereo + ); + + /// return nominal input sample requirement for triggering a processing batch + int getInputSampleReq() const + { + return (int)(nominalSkip + 0.5); + } + + /// return nominal output sample amount when running a processing batch + int getOutputBatchSize() const + { + return seekWindowLength - overlapLength; + } +}; + + + +// Implementation-specific class declarations: + +#ifdef SOUNDTOUCH_ALLOW_MMX + /// Class that implements MMX optimized routines for 16bit integer samples type. + class TDStretchMMX : public TDStretch + { + protected: + double calcCrossCorr(const short *mixingPos, const short *compare) const; + virtual void overlapStereo(short *output, const short *input) const; + virtual void clearCrossCorrState(); + }; +#endif /// SOUNDTOUCH_ALLOW_MMX + + +#ifdef SOUNDTOUCH_ALLOW_SSE + /// Class that implements SSE optimized routines for floating point samples type. + class TDStretchSSE : public TDStretch + { + protected: + double calcCrossCorr(const float *mixingPos, const float *compare) const; + }; + +#endif /// SOUNDTOUCH_ALLOW_SSE + +} +#endif /// TDStretch_H diff --git a/media/libsoundtouch/src/cpu_detect.h b/media/libsoundtouch/src/cpu_detect.h new file mode 100644 index 00000000000..b55569060fc --- /dev/null +++ b/media/libsoundtouch/src/cpu_detect.h @@ -0,0 +1,62 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// A header file for detecting the Intel MMX instructions set extension. +/// +/// Please see 'mmx_win.cpp', 'mmx_cpp.cpp' and 'mmx_non_x86.cpp' for the +/// routine implementations for x86 Windows, x86 gnu version and non-x86 +/// platforms, respectively. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2008-02-10 08:26:55 -0800 (Sun, 10 Feb 2008) $ +// File revision : $Revision: 4 $ +// +// $Id: cpu_detect.h 11 2008-02-10 16:26:55Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _CPU_DETECT_H_ +#define _CPU_DETECT_H_ + +#include "STTypes.h" + +#define SUPPORT_MMX 0x0001 +#define SUPPORT_3DNOW 0x0002 +#define SUPPORT_ALTIVEC 0x0004 +#define SUPPORT_SSE 0x0008 +#define SUPPORT_SSE2 0x0010 + +/// Checks which instruction set extensions are supported by the CPU. +/// +/// \return A bitmask of supported extensions, see SUPPORT_... defines. +uint detectCPUextensions(void); + +/// Disables given set of instruction extensions. See SUPPORT_... defines. +void disableExtensions(uint wDisableMask); + +#endif // _CPU_DETECT_H_ diff --git a/media/libsoundtouch/src/cpu_detect_x86.cpp b/media/libsoundtouch/src/cpu_detect_x86.cpp new file mode 100644 index 00000000000..d15c6bbace6 --- /dev/null +++ b/media/libsoundtouch/src/cpu_detect_x86.cpp @@ -0,0 +1,144 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Generic version of the x86 CPU extension detection routine. +/// +/// This file is for GNU & other non-Windows compilers, see 'cpu_detect_x86_win.cpp' +/// for the Microsoft compiler version. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 13:00:09 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: cpu_detect_x86.cpp 138 2012-04-01 20:00:09Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include "cpu_detect.h" +#include "STTypes.h" + +#if defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) + #if defined(__GNUC__) + // gcc and clang + #include "cpuid.h" + #endif + + #if defined(_M_IX86) + // windows + #include + #endif + // If we still don't have the macros, define them (Windows, MacOS) + #ifndef bit_MMX + #define bit_MMX (1 << 23) + #endif + #ifndef bit_SSE + #define bit_SSE (1 << 25) + #endif + #ifndef bit_SSE2 + #define bit_SSE2 (1 << 26) + #endif +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// processor instructions extension detection routines +// +////////////////////////////////////////////////////////////////////////////// + +// Flag variable indicating whick ISA extensions are disabled (for debugging) +static uint _dwDisabledISA = 0x00; // 0xffffffff; //<- use this to disable all extensions + +// Disables given set of instruction extensions. See SUPPORT_... defines. +void disableExtensions(uint dwDisableMask) +{ + _dwDisabledISA = dwDisableMask; +} + + + +/// Checks which instruction set extensions are supported by the CPU. +uint detectCPUextensions(void) +{ +/// If building for a 64bit system (no Itanium) and the user wants optimizations. +/// Return the OR of SUPPORT_{MMX,SSE,SSE2}. 11001 or 0x19. +/// Keep the _dwDisabledISA test (2 more operations, could be eliminated). +#if ((defined(__GNUC__) && defined(__x86_64__)) \ + || defined(_M_X64)) \ + && defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) + return 0x19 & ~_dwDisabledISA; + +/// If building for a 32bit system and the user wants optimizations. +/// Keep the _dwDisabledISA test (2 more operations, could be eliminated). +#elif ((defined(__GNUC__) && defined(__i386__)) \ + || defined(_M_IX86)) \ + && defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) + + if (_dwDisabledISA == 0xffffffff) return 0; + + uint res = 0; + +#if defined(__GNUC__) + // GCC version of cpuid. Requires GCC 4.3.0 or later for __cpuid intrinsic support. + uint eax, ebx, ecx, edx; // unsigned int is the standard type. uint is defined by the compiler and not guaranteed to be portable. + + // Check if no cpuid support. + if (!__get_cpuid (1, &eax, &ebx, &ecx, &edx)) return 0; // always disable extensions. + + if (edx & bit_MMX) res = res | SUPPORT_MMX; + if (edx & bit_SSE) res = res | SUPPORT_SSE; + if (edx & bit_SSE2) res = res | SUPPORT_SSE2; + +#else + // Window / VS version of cpuid. Notice that Visual Studio 2005 or later required + // for __cpuid intrinsic support. + int reg[4] = {-1}; + + // Check if no cpuid support. + __cpuid(reg,0); + if ((unsigned int)reg[0] == 0) return 0; // always disable extensions. + + __cpuid(reg,1); + if ((unsigned int)reg[3] & bit_MMX) res = res | SUPPORT_MMX; + if ((unsigned int)reg[3] & bit_SSE) res = res | SUPPORT_SSE; + if ((unsigned int)reg[3] & bit_SSE2) res = res | SUPPORT_SSE2; + +#endif + + return res & ~_dwDisabledISA; + +#else + +/// One of these is true: +/// 1) We don't want optimizations. +/// 2) Using an unsupported compiler. +/// 3) Running on a non-x86 platform. + return 0; + +#endif +} diff --git a/media/libsoundtouch/src/mmx_optimized.cpp b/media/libsoundtouch/src/mmx_optimized.cpp new file mode 100644 index 00000000000..7cb65edd3e3 --- /dev/null +++ b/media/libsoundtouch/src/mmx_optimized.cpp @@ -0,0 +1,317 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// MMX optimized routines. All MMX optimized functions have been gathered into +/// this single source code file, regardless to their class or original source +/// code file, in order to ease porting the library to other compiler and +/// processor platforms. +/// +/// The MMX-optimizations are programmed using MMX compiler intrinsics that +/// are supported both by Microsoft Visual C++ and GCC compilers, so this file +/// should compile with both toolsets. +/// +/// NOTICE: If using Visual Studio 6.0, you'll need to install the "Visual C++ +/// 6.0 processor pack" update to support compiler intrinsic syntax. The update +/// is available for download at Microsoft Developers Network, see here: +/// http://msdn.microsoft.com/en-us/vstudio/aa718349.aspx +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: mmx_optimized.cpp 137 2012-04-01 19:49:30Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include "STTypes.h" + +#ifdef SOUNDTOUCH_ALLOW_MMX +// MMX routines available only with integer sample type + +using namespace soundtouch; + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of MMX optimized functions of class 'TDStretchMMX' +// +////////////////////////////////////////////////////////////////////////////// + +#include "TDStretch.h" +#include +#include +#include + + +// Calculates cross correlation of two buffers +double TDStretchMMX::calcCrossCorr(const short *pV1, const short *pV2) const +{ + const __m64 *pVec1, *pVec2; + __m64 shifter; + __m64 accu, normaccu; + long corr, norm; + int i; + + pVec1 = (__m64*)pV1; + pVec2 = (__m64*)pV2; + + shifter = _m_from_int(overlapDividerBits); + normaccu = accu = _mm_setzero_si64(); + + // Process 4 parallel sets of 2 * stereo samples or 4 * mono samples + // during each round for improved CPU-level parallellization. + for (i = 0; i < channels * overlapLength / 16; i ++) + { + __m64 temp, temp2; + + // dictionary of instructions: + // _m_pmaddwd : 4*16bit multiply-add, resulting two 32bits = [a0*b0+a1*b1 ; a2*b2+a3*b3] + // _mm_add_pi32 : 2*32bit add + // _m_psrad : 32bit right-shift + + temp = _mm_add_pi32(_mm_madd_pi16(pVec1[0], pVec2[0]), + _mm_madd_pi16(pVec1[1], pVec2[1])); + temp2 = _mm_add_pi32(_mm_madd_pi16(pVec1[0], pVec1[0]), + _mm_madd_pi16(pVec1[1], pVec1[1])); + accu = _mm_add_pi32(accu, _mm_sra_pi32(temp, shifter)); + normaccu = _mm_add_pi32(normaccu, _mm_sra_pi32(temp2, shifter)); + + temp = _mm_add_pi32(_mm_madd_pi16(pVec1[2], pVec2[2]), + _mm_madd_pi16(pVec1[3], pVec2[3])); + temp2 = _mm_add_pi32(_mm_madd_pi16(pVec1[2], pVec1[2]), + _mm_madd_pi16(pVec1[3], pVec1[3])); + accu = _mm_add_pi32(accu, _mm_sra_pi32(temp, shifter)); + normaccu = _mm_add_pi32(normaccu, _mm_sra_pi32(temp2, shifter)); + + pVec1 += 4; + pVec2 += 4; + } + + // copy hi-dword of mm0 to lo-dword of mm1, then sum mmo+mm1 + // and finally store the result into the variable "corr" + + accu = _mm_add_pi32(accu, _mm_srli_si64(accu, 32)); + corr = _m_to_int(accu); + + normaccu = _mm_add_pi32(normaccu, _mm_srli_si64(normaccu, 32)); + norm = _m_to_int(normaccu); + + // Clear MMS state + _m_empty(); + + // Normalize result by dividing by sqrt(norm) - this step is easiest + // done using floating point operation + if (norm == 0) norm = 1; // to avoid div by zero + + return (double)corr / sqrt((double)norm); + // Note: Warning about the missing EMMS instruction is harmless + // as it'll be called elsewhere. +} + + + +void TDStretchMMX::clearCrossCorrState() +{ + // Clear MMS state + _m_empty(); + //_asm EMMS; +} + + + +// MMX-optimized version of the function overlapStereo +void TDStretchMMX::overlapStereo(short *output, const short *input) const +{ + const __m64 *pVinput, *pVMidBuf; + __m64 *pVdest; + __m64 mix1, mix2, adder, shifter; + int i; + + pVinput = (const __m64*)input; + pVMidBuf = (const __m64*)pMidBuffer; + pVdest = (__m64*)output; + + // mix1 = mixer values for 1st stereo sample + // mix1 = mixer values for 2nd stereo sample + // adder = adder for updating mixer values after each round + + mix1 = _mm_set_pi16(0, overlapLength, 0, overlapLength); + adder = _mm_set_pi16(1, -1, 1, -1); + mix2 = _mm_add_pi16(mix1, adder); + adder = _mm_add_pi16(adder, adder); + + // Overlaplength-division by shifter. "+1" is to account for "-1" deduced in + // overlapDividerBits calculation earlier. + shifter = _m_from_int(overlapDividerBits + 1); + + for (i = 0; i < overlapLength / 4; i ++) + { + __m64 temp1, temp2; + + // load & shuffle data so that input & mixbuffer data samples are paired + temp1 = _mm_unpacklo_pi16(pVMidBuf[0], pVinput[0]); // = i0l m0l i0r m0r + temp2 = _mm_unpackhi_pi16(pVMidBuf[0], pVinput[0]); // = i1l m1l i1r m1r + + // temp = (temp .* mix) >> shifter + temp1 = _mm_sra_pi32(_mm_madd_pi16(temp1, mix1), shifter); + temp2 = _mm_sra_pi32(_mm_madd_pi16(temp2, mix2), shifter); + pVdest[0] = _mm_packs_pi32(temp1, temp2); // pack 2*2*32bit => 4*16bit + + // update mix += adder + mix1 = _mm_add_pi16(mix1, adder); + mix2 = _mm_add_pi16(mix2, adder); + + // --- second round begins here --- + + // load & shuffle data so that input & mixbuffer data samples are paired + temp1 = _mm_unpacklo_pi16(pVMidBuf[1], pVinput[1]); // = i2l m2l i2r m2r + temp2 = _mm_unpackhi_pi16(pVMidBuf[1], pVinput[1]); // = i3l m3l i3r m3r + + // temp = (temp .* mix) >> shifter + temp1 = _mm_sra_pi32(_mm_madd_pi16(temp1, mix1), shifter); + temp2 = _mm_sra_pi32(_mm_madd_pi16(temp2, mix2), shifter); + pVdest[1] = _mm_packs_pi32(temp1, temp2); // pack 2*2*32bit => 4*16bit + + // update mix += adder + mix1 = _mm_add_pi16(mix1, adder); + mix2 = _mm_add_pi16(mix2, adder); + + pVinput += 2; + pVMidBuf += 2; + pVdest += 2; + } + + _m_empty(); // clear MMS state +} + + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of MMX optimized functions of class 'FIRFilter' +// +////////////////////////////////////////////////////////////////////////////// + +#include "FIRFilter.h" + + +FIRFilterMMX::FIRFilterMMX() : FIRFilter() +{ + filterCoeffsUnalign = NULL; +} + + +FIRFilterMMX::~FIRFilterMMX() +{ + delete[] filterCoeffsUnalign; +} + + +// (overloaded) Calculates filter coefficients for MMX routine +void FIRFilterMMX::setCoefficients(const short *coeffs, uint newLength, uint uResultDivFactor) +{ + uint i; + FIRFilter::setCoefficients(coeffs, newLength, uResultDivFactor); + + // Ensure that filter coeffs array is aligned to 16-byte boundary + delete[] filterCoeffsUnalign; + filterCoeffsUnalign = new short[2 * newLength + 8]; + filterCoeffsAlign = (short *)(((ulong)filterCoeffsUnalign + 15) & -16); + + // rearrange the filter coefficients for mmx routines + for (i = 0;i < length; i += 4) + { + filterCoeffsAlign[2 * i + 0] = coeffs[i + 0]; + filterCoeffsAlign[2 * i + 1] = coeffs[i + 2]; + filterCoeffsAlign[2 * i + 2] = coeffs[i + 0]; + filterCoeffsAlign[2 * i + 3] = coeffs[i + 2]; + + filterCoeffsAlign[2 * i + 4] = coeffs[i + 1]; + filterCoeffsAlign[2 * i + 5] = coeffs[i + 3]; + filterCoeffsAlign[2 * i + 6] = coeffs[i + 1]; + filterCoeffsAlign[2 * i + 7] = coeffs[i + 3]; + } +} + + + +// mmx-optimized version of the filter routine for stereo sound +uint FIRFilterMMX::evaluateFilterStereo(short *dest, const short *src, uint numSamples) const +{ + // Create stack copies of the needed member variables for asm routines : + uint i, j; + __m64 *pVdest = (__m64*)dest; + + if (length < 2) return 0; + + for (i = 0; i < (numSamples - length) / 2; i ++) + { + __m64 accu1; + __m64 accu2; + const __m64 *pVsrc = (const __m64*)src; + const __m64 *pVfilter = (const __m64*)filterCoeffsAlign; + + accu1 = accu2 = _mm_setzero_si64(); + for (j = 0; j < lengthDiv8 * 2; j ++) + { + __m64 temp1, temp2; + + temp1 = _mm_unpacklo_pi16(pVsrc[0], pVsrc[1]); // = l2 l0 r2 r0 + temp2 = _mm_unpackhi_pi16(pVsrc[0], pVsrc[1]); // = l3 l1 r3 r1 + + accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp1, pVfilter[0])); // += l2*f2+l0*f0 r2*f2+r0*f0 + accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp2, pVfilter[1])); // += l3*f3+l1*f1 r3*f3+r1*f1 + + temp1 = _mm_unpacklo_pi16(pVsrc[1], pVsrc[2]); // = l4 l2 r4 r2 + + accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp2, pVfilter[0])); // += l3*f2+l1*f0 r3*f2+r1*f0 + accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp1, pVfilter[1])); // += l4*f3+l2*f1 r4*f3+r2*f1 + + // accu1 += l2*f2+l0*f0 r2*f2+r0*f0 + // += l3*f3+l1*f1 r3*f3+r1*f1 + + // accu2 += l3*f2+l1*f0 r3*f2+r1*f0 + // l4*f3+l2*f1 r4*f3+r2*f1 + + pVfilter += 2; + pVsrc += 2; + } + // accu >>= resultDivFactor + accu1 = _mm_srai_pi32(accu1, resultDivFactor); + accu2 = _mm_srai_pi32(accu2, resultDivFactor); + + // pack 2*2*32bits => 4*16 bits + pVdest[0] = _mm_packs_pi32(accu1, accu2); + src += 4; + pVdest ++; + } + + _m_empty(); // clear emms state + + return (numSamples & 0xfffffffe) - length; +} + +#endif // SOUNDTOUCH_ALLOW_MMX diff --git a/media/libsoundtouch/src/soundtouch.rc b/media/libsoundtouch/src/soundtouch.rc new file mode 100644 index 00000000000..313039b14bf --- /dev/null +++ b/media/libsoundtouch/src/soundtouch.rc @@ -0,0 +1,53 @@ + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +// Note: if you contain versioning information in an included +// RC script, it will be discarded +// Use module.ver to explicitly set these values + +// Do not edit this file. Changes won't affect the build. + + + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,7,0,0 + PRODUCTVERSION 1,7,0,0 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "Comments", "SoundTouch Library licensed for 3rd party applications subject to LGPL license v2.1. Visit http://www.surina.net/soundtouch for more information about the SoundTouch library." + VALUE "FileDescription", "SoundTouch Dynamic Link Library" + VALUE "FileVersion", "1, 7, 0, 0" + VALUE "InternalName", "SoundTouch" + VALUE "LegalCopyright", "Copyright (C) Olli Parviainen 1999-2012" + VALUE "OriginalFilename", "SoundTouch.dll" + VALUE "ProductName", " SoundTouch Dynamic Link Library" + VALUE "ProductVersion", "1, 7, 0, 0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END + diff --git a/media/libsoundtouch/src/soundtouch_config.h b/media/libsoundtouch/src/soundtouch_config.h new file mode 100644 index 00000000000..6807a1a21dc --- /dev/null +++ b/media/libsoundtouch/src/soundtouch_config.h @@ -0,0 +1,19 @@ +#include "mozilla/SSE.h" + +#ifdef MOZ_SAMPLE_TYPE_FLOAT32 +#define SOUNDTOUCH_FLOAT_SAMPLES 1 +#else +#define SOUNDTOUCH_INTEGER_SAMPLES 1 +#endif + +#ifndef MOZILLA_PRESUME_SSE +#ifdef MOZ_SAMPLE_TYPE_FLOAT32 +#define SOUNDTOUCH_DISABLE_X86_OPTIMIZATIONS 1 +#endif +#endif + +#ifndef MOZILLA_PRESUME_MMX +#ifdef MOZ_SAMPLE_TYPE_S16LE +#define SOUNDTOUCH_DISABLE_X86_OPTIMIZATIONS 1 +#endif +#endif diff --git a/media/libsoundtouch/src/sse_optimized.cpp b/media/libsoundtouch/src/sse_optimized.cpp new file mode 100644 index 00000000000..ea0d9b6b0a4 --- /dev/null +++ b/media/libsoundtouch/src/sse_optimized.cpp @@ -0,0 +1,361 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// SSE optimized routines for Pentium-III, Athlon-XP and later CPUs. All SSE +/// optimized functions have been gathered into this single source +/// code file, regardless to their class or original source code file, in order +/// to ease porting the library to other compiler and processor platforms. +/// +/// The SSE-optimizations are programmed using SSE compiler intrinsics that +/// are supported both by Microsoft Visual C++ and GCC compilers, so this file +/// should compile with both toolsets. +/// +/// NOTICE: If using Visual Studio 6.0, you'll need to install the "Visual C++ +/// 6.0 processor pack" update to support SSE instruction set. The update is +/// available for download at Microsoft Developers Network, see here: +/// http://msdn.microsoft.com/en-us/vstudio/aa718349.aspx +/// +/// If the above URL is expired or removed, go to "http://msdn.microsoft.com" and +/// perform a search with keywords "processor pack". +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 12:49:30 -0700 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: sse_optimized.cpp 137 2012-04-01 19:49:30Z oparviai $ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include "cpu_detect.h" +#include "STTypes.h" + +using namespace soundtouch; + +#ifdef SOUNDTOUCH_ALLOW_SSE + +// SSE routines available only with float sample type + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of SSE optimized functions of class 'TDStretchSSE' +// +////////////////////////////////////////////////////////////////////////////// + +#include "TDStretch.h" +#include +#include + +// Calculates cross correlation of two buffers +double TDStretchSSE::calcCrossCorr(const float *pV1, const float *pV2) const +{ + int i; + const float *pVec1; + const __m128 *pVec2; + __m128 vSum, vNorm; + + // Note. It means a major slow-down if the routine needs to tolerate + // unaligned __m128 memory accesses. It's way faster if we can skip + // unaligned slots and use _mm_load_ps instruction instead of _mm_loadu_ps. + // This can mean up to ~ 10-fold difference (incl. part of which is + // due to skipping every second round for stereo sound though). + // + // Compile-time define SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION is provided + // for choosing if this little cheating is allowed. + +#ifdef SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION + // Little cheating allowed, return valid correlation only for + // aligned locations, meaning every second round for stereo sound. + + #define _MM_LOAD _mm_load_ps + + if (((ulong)pV1) & 15) return -1e50; // skip unaligned locations + +#else + // No cheating allowed, use unaligned load & take the resulting + // performance hit. + #define _MM_LOAD _mm_loadu_ps +#endif + + // ensure overlapLength is divisible by 8 + assert((overlapLength % 8) == 0); + + // Calculates the cross-correlation value between 'pV1' and 'pV2' vectors + // Note: pV2 _must_ be aligned to 16-bit boundary, pV1 need not. + pVec1 = (const float*)pV1; + pVec2 = (const __m128*)pV2; + vSum = vNorm = _mm_setzero_ps(); + + // Unroll the loop by factor of 4 * 4 operations. Use same routine for + // stereo & mono, for mono it just means twice the amount of unrolling. + for (i = 0; i < channels * overlapLength / 16; i ++) + { + __m128 vTemp; + // vSum += pV1[0..3] * pV2[0..3] + vTemp = _MM_LOAD(pVec1); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp ,pVec2[0])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + // vSum += pV1[4..7] * pV2[4..7] + vTemp = _MM_LOAD(pVec1 + 4); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[1])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + // vSum += pV1[8..11] * pV2[8..11] + vTemp = _MM_LOAD(pVec1 + 8); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[2])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + // vSum += pV1[12..15] * pV2[12..15] + vTemp = _MM_LOAD(pVec1 + 12); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[3])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + pVec1 += 16; + pVec2 += 4; + } + + // return value = vSum[0] + vSum[1] + vSum[2] + vSum[3] + float *pvNorm = (float*)&vNorm; + double norm = sqrt(pvNorm[0] + pvNorm[1] + pvNorm[2] + pvNorm[3]); + if (norm < 1e-9) norm = 1.0; // to avoid div by zero + + float *pvSum = (float*)&vSum; + return (double)(pvSum[0] + pvSum[1] + pvSum[2] + pvSum[3]) / norm; + + /* This is approximately corresponding routine in C-language yet without normalization: + double corr, norm; + uint i; + + // Calculates the cross-correlation value between 'pV1' and 'pV2' vectors + corr = norm = 0.0; + for (i = 0; i < channels * overlapLength / 16; i ++) + { + corr += pV1[0] * pV2[0] + + pV1[1] * pV2[1] + + pV1[2] * pV2[2] + + pV1[3] * pV2[3] + + pV1[4] * pV2[4] + + pV1[5] * pV2[5] + + pV1[6] * pV2[6] + + pV1[7] * pV2[7] + + pV1[8] * pV2[8] + + pV1[9] * pV2[9] + + pV1[10] * pV2[10] + + pV1[11] * pV2[11] + + pV1[12] * pV2[12] + + pV1[13] * pV2[13] + + pV1[14] * pV2[14] + + pV1[15] * pV2[15]; + + for (j = 0; j < 15; j ++) norm += pV1[j] * pV1[j]; + + pV1 += 16; + pV2 += 16; + } + return corr / sqrt(norm); + */ +} + + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of SSE optimized functions of class 'FIRFilter' +// +////////////////////////////////////////////////////////////////////////////// + +#include "FIRFilter.h" + +FIRFilterSSE::FIRFilterSSE() : FIRFilter() +{ + filterCoeffsAlign = NULL; + filterCoeffsUnalign = NULL; +} + + +FIRFilterSSE::~FIRFilterSSE() +{ + delete[] filterCoeffsUnalign; + filterCoeffsAlign = NULL; + filterCoeffsUnalign = NULL; +} + + +// (overloaded) Calculates filter coefficients for SSE routine +void FIRFilterSSE::setCoefficients(const float *coeffs, uint newLength, uint uResultDivFactor) +{ + uint i; + float fDivider; + + FIRFilter::setCoefficients(coeffs, newLength, uResultDivFactor); + + // Scale the filter coefficients so that it won't be necessary to scale the filtering result + // also rearrange coefficients suitably for SSE + // Ensure that filter coeffs array is aligned to 16-byte boundary + delete[] filterCoeffsUnalign; + filterCoeffsUnalign = new float[2 * newLength + 4]; + filterCoeffsAlign = (float *)(((unsigned long)filterCoeffsUnalign + 15) & (ulong)-16); + + fDivider = (float)resultDivider; + + // rearrange the filter coefficients for mmx routines + for (i = 0; i < newLength; i ++) + { + filterCoeffsAlign[2 * i + 0] = + filterCoeffsAlign[2 * i + 1] = coeffs[i + 0] / fDivider; + } +} + + + +// SSE-optimized version of the filter routine for stereo sound +uint FIRFilterSSE::evaluateFilterStereo(float *dest, const float *source, uint numSamples) const +{ + int count = (int)((numSamples - length) & (uint)-2); + int j; + + assert(count % 2 == 0); + + if (count < 2) return 0; + + assert(source != NULL); + assert(dest != NULL); + assert((length % 8) == 0); + assert(filterCoeffsAlign != NULL); + assert(((ulong)filterCoeffsAlign) % 16 == 0); + + // filter is evaluated for two stereo samples with each iteration, thus use of 'j += 2' + for (j = 0; j < count; j += 2) + { + const float *pSrc; + const __m128 *pFil; + __m128 sum1, sum2; + uint i; + + pSrc = (const float*)source; // source audio data + pFil = (const __m128*)filterCoeffsAlign; // filter coefficients. NOTE: Assumes coefficients + // are aligned to 16-byte boundary + sum1 = sum2 = _mm_setzero_ps(); + + for (i = 0; i < length / 8; i ++) + { + // Unroll loop for efficiency & calculate filter for 2*2 stereo samples + // at each pass + + // sum1 is accu for 2*2 filtered stereo sound data at the primary sound data offset + // sum2 is accu for 2*2 filtered stereo sound data for the next sound sample offset. + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc) , pFil[0])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 2), pFil[0])); + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 4), pFil[1])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 6), pFil[1])); + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 8) , pFil[2])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 10), pFil[2])); + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 12), pFil[3])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 14), pFil[3])); + + pSrc += 16; + pFil += 4; + } + + // Now sum1 and sum2 both have a filtered 2-channel sample each, but we still need + // to sum the two hi- and lo-floats of these registers together. + + // post-shuffle & add the filtered values and store to dest. + _mm_storeu_ps(dest, _mm_add_ps( + _mm_shuffle_ps(sum1, sum2, _MM_SHUFFLE(1,0,3,2)), // s2_1 s2_0 s1_3 s1_2 + _mm_shuffle_ps(sum1, sum2, _MM_SHUFFLE(3,2,1,0)) // s2_3 s2_2 s1_1 s1_0 + )); + source += 4; + dest += 4; + } + + // Ideas for further improvement: + // 1. If it could be guaranteed that 'source' were always aligned to 16-byte + // boundary, a faster aligned '_mm_load_ps' instruction could be used. + // 2. If it could be guaranteed that 'dest' were always aligned to 16-byte + // boundary, a faster '_mm_store_ps' instruction could be used. + + return (uint)count; + + /* original routine in C-language. please notice the C-version has differently + organized coefficients though. + double suml1, suml2; + double sumr1, sumr2; + uint i, j; + + for (j = 0; j < count; j += 2) + { + const float *ptr; + const float *pFil; + + suml1 = sumr1 = 0.0; + suml2 = sumr2 = 0.0; + ptr = src; + pFil = filterCoeffs; + for (i = 0; i < lengthLocal; i ++) + { + // unroll loop for efficiency. + + suml1 += ptr[0] * pFil[0] + + ptr[2] * pFil[2] + + ptr[4] * pFil[4] + + ptr[6] * pFil[6]; + + sumr1 += ptr[1] * pFil[1] + + ptr[3] * pFil[3] + + ptr[5] * pFil[5] + + ptr[7] * pFil[7]; + + suml2 += ptr[8] * pFil[0] + + ptr[10] * pFil[2] + + ptr[12] * pFil[4] + + ptr[14] * pFil[6]; + + sumr2 += ptr[9] * pFil[1] + + ptr[11] * pFil[3] + + ptr[13] * pFil[5] + + ptr[15] * pFil[7]; + + ptr += 16; + pFil += 8; + } + dest[0] = (float)suml1; + dest[1] = (float)sumr1; + dest[2] = (float)suml2; + dest[3] = (float)sumr2; + + src += 4; + dest += 4; + } + */ +} + +#endif // SOUNDTOUCH_ALLOW_SSE diff --git a/media/libsoundtouch/update.sh b/media/libsoundtouch/update.sh new file mode 100644 index 00000000000..9940601bcd0 --- /dev/null +++ b/media/libsoundtouch/update.sh @@ -0,0 +1,40 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Usage: ./update.sh +# +# Copies the needed files from a directory containing the original +# soundtouch sources that we need for HTML5 media playback rate change. + +cp $1/COPYING.TXT . +cp $1/source/SoundTouch/AAFilter.cpp src +cp $1/source/SoundTouch/AAFilter.h src +cp $1/source/SoundTouch/cpu_detect.h src +cp $1/source/SoundTouch/cpu_detect_x86.cpp src +cp $1/source/SoundTouch/FIFOSampleBuffer.cpp src +cp $1/source/SoundTouch/FIRFilter.cpp src +cp $1/source/SoundTouch/FIRFilter.h src +cp $1/source/SoundTouch/mmx_optimized.cpp src +cp $1/source/SoundTouch/RateTransposer.cpp src +cp $1/source/SoundTouch/RateTransposer.h src +cp $1/source/SoundTouch/SoundTouch.cpp src +cp $1/source/SoundTouch/sse_optimized.cpp src +cp $1/source/SoundTouch/TDStretch.cpp src +cp $1/source/SoundTouch/TDStretch.h src +cp $1/include/SoundTouch.h src +cp $1/include/FIFOSampleBuffer.h src +cp $1/include/FIFOSamplePipe.h src +cp $1/include/SoundTouch.h src +cp $1/include/STTypes.h src + +# Remote the Windows line ending characters from the files. +for i in src/* +do + cat $i | tr -d '\015' > $i.lf + mv $i.lf $i +done + +# Patch the imported files. +patch -p1 < moz-libsoundtouch.patch + diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 7253073b50e..9d7ed11b58d 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -40,6 +40,7 @@ @BINPATH@/@DLL_PREFIX@xpcom@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@omxplugin@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@xul@DLL_SUFFIX@ diff --git a/mobile/xul/installer/package-manifest.in b/mobile/xul/installer/package-manifest.in index 8b0fd4892d8..94ea7af4bec 100644 --- a/mobile/xul/installer/package-manifest.in +++ b/mobile/xul/installer/package-manifest.in @@ -51,6 +51,7 @@ @BINPATH@/@DLL_PREFIX@xpcom@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozalloc@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@soundtouch@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@omxplugin@DLL_SUFFIX@ #ifdef XP_MACOSX diff --git a/toolkit/content/license.html b/toolkit/content/license.html index b2b4a1b881b..e1888afb612 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -87,6 +87,7 @@

  • libffi License
  • libjingle License
  • libnestegg License
  • +
  • libsoundtouch License
  • libsrtp License
  • libunwind License
  • libyuv License
  • @@ -1967,6 +1968,32 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    +

    libsoundtouch License

    + +

    This license applies to certain files in the directory + media/libsoundtouch/src/. +

    + +
    +The SoundTouch Library Copyright © Olli Parviainen 2001-2012
    +
    +This library is free software; you can redistribute it and/or
    +modify it under the terms of the GNU Lesser General Public
    +License as published by the Free Software Foundation; either
    +version 2.1 of the License, or (at your option) any later version.
    +
    +This library is distributed in the hope that it will be useful,
    +but WITHOUT ANY WARRANTY; without even the implied warranty of
    +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    +Lesser General Public License for more details.
    +
    +You should have received a copy of the GNU Lesser General Public
    +License along with this library; if not, write to the Free Software
    +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
    +
    + +
    +

    libsrtp License

    This license applies to files in the directory diff --git a/toolkit/library/Makefile.in b/toolkit/library/Makefile.in index 8ddeb2cb1f6..677f4540806 100644 --- a/toolkit/library/Makefile.in +++ b/toolkit/library/Makefile.in @@ -358,6 +358,7 @@ EXTRA_DSO_LDOPTS += \ $(MOZ_CAIRO_OSLIBS) \ $(MOZ_APP_EXTRA_LIBS) \ $(SQLITE_LIBS) \ + $(SOUNDTOUCH_LIBS) \ $(NULL) ifdef MOZ_NATIVE_JPEG diff --git a/toolkit/mozapps/installer/packager.mk b/toolkit/mozapps/installer/packager.mk index 6da907a07ef..dd63f6de6b9 100644 --- a/toolkit/mozapps/installer/packager.mk +++ b/toolkit/mozapps/installer/packager.mk @@ -262,6 +262,7 @@ DIST_FILES += \ libplc4.so \ libplds4.so \ libmozsqlite3.so \ + libsoundtouch.so \ libnssutil3.so \ libnss3.so \ libssl3.so \ diff --git a/toolkit/toolkit-makefiles.sh b/toolkit/toolkit-makefiles.sh index 91d25475aee..5ae3a99c9d9 100644 --- a/toolkit/toolkit-makefiles.sh +++ b/toolkit/toolkit-makefiles.sh @@ -1737,3 +1737,11 @@ if [ "$MOZ_SPEEX_RESAMPLER" ]; then media/libspeex_resampler/src/Makefile " fi + +if [ "$MOZ_SOUNDTOUCH" ]; then + add_makefiles " + media/libsoundtouch/Makefile + media/libsoundtouch/src/Makefile + " +fi + diff --git a/toolkit/toolkit-tiers.mk b/toolkit/toolkit-tiers.mk index 4b16d2380c1..aecd4f56e69 100644 --- a/toolkit/toolkit-tiers.mk +++ b/toolkit/toolkit-tiers.mk @@ -151,6 +151,12 @@ tier_platform_dirs += \ $(NULL) endif +ifdef MOZ_SOUNDTOUCH +tier_platform_dirs += \ + media/libsoundtouch \ + $(NULL) +endif + ifdef MOZ_CUBEB tier_platform_dirs += \ media/libcubeb \ From af422317a550afedf53c49dcbc77239d85a00f48 Mon Sep 17 00:00:00 2001 From: Robert Longson Date: Wed, 7 Nov 2012 09:53:44 +0000 Subject: [PATCH 11/77] Bug 779971 - Make nsSVGTextPathProperty::DoUpdate trigger nsSVGTextFrame::NotifyGlyphMetricsChange() off an asynchronous change hint (to avoid calling nsLayoutUtils::FrameNeedsReflow synchronously under nsISVGChildFrame::ReflowSVG or during frame teardown, and avoid infinite loops caused by using an event queue event). r=jwatt. --- layout/base/nsCSSFrameConstructor.cpp | 7 +++++ layout/base/nsChangeHint.h | 8 +++++- layout/svg/crashtests/779971-1.svg | 14 ++++++++++ layout/svg/crashtests/crashtests.list | 1 + layout/svg/nsSVGEffects.cpp | 38 ++++----------------------- 5 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 layout/svg/crashtests/779971-1.svg diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 2c191a0383f..0cf2d45ae6c 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -116,6 +116,7 @@ #include "nsIDOMSVGFilters.h" #include "DOMSVGTests.h" #include "nsSVGEffects.h" +#include "nsSVGTextPathFrame.h" #include "nsSVGUtils.h" #include "nsRefreshDriver.h" @@ -7787,6 +7788,12 @@ DoApplyRenderingChangeToTree(nsIFrame* aFrame, needInvalidatingPaint = true; // Invalidate and update our area: nsSVGUtils::InvalidateAndScheduleReflowSVG(aFrame); + } else if (aChange & nsChangeHint_UpdateTextPath) { + NS_ABORT_IF_FALSE(aFrame->GetType() == nsGkAtoms::svgTextPathFrame, + "textPath frame expected"); + needInvalidatingPaint = true; + // Invalidate and update our area: + static_cast(aFrame)->NotifyGlyphMetricsChange(); } else { needInvalidatingPaint = true; // Just invalidate our area: diff --git a/layout/base/nsChangeHint.h b/layout/base/nsChangeHint.h index cb055fb1f97..977eae84acf 100644 --- a/layout/base/nsChangeHint.h +++ b/layout/base/nsChangeHint.h @@ -111,7 +111,13 @@ enum nsChangeHint { * changes, and it's inherited by a child, that might require a reflow * due to the border-width change on the child. */ - nsChangeHint_BorderStyleNoneChange = 0x8000 + nsChangeHint_BorderStyleNoneChange = 0x8000, + + /** + * SVG textPath needs to be recomputed because the path has changed. + * This means that the glyph positions of the text need to be recomputed. + */ + nsChangeHint_UpdateTextPath = 0x10000 // IMPORTANT NOTE: When adding new hints, consider whether you need to // add them to NS_HintsNotHandledForDescendantsIn() below. diff --git a/layout/svg/crashtests/779971-1.svg b/layout/svg/crashtests/779971-1.svg new file mode 100644 index 00000000000..d57065a0ba8 --- /dev/null +++ b/layout/svg/crashtests/779971-1.svg @@ -0,0 +1,14 @@ + +x1 + + diff --git a/layout/svg/crashtests/crashtests.list b/layout/svg/crashtests/crashtests.list index d01bb92fee5..e026a384623 100644 --- a/layout/svg/crashtests/crashtests.list +++ b/layout/svg/crashtests/crashtests.list @@ -136,6 +136,7 @@ load 767056-1.svg load 768351.svg load 780963-1.html load 757751-1.svg +load 779971-1.svg load 782141-1.svg load 784061-1.svg load 790072.svg diff --git a/layout/svg/nsSVGEffects.cpp b/layout/svg/nsSVGEffects.cpp index e307665c643..03ae1891811 100644 --- a/layout/svg/nsSVGEffects.cpp +++ b/layout/svg/nsSVGEffects.cpp @@ -286,32 +286,6 @@ nsSVGMarkerProperty::DoUpdate() mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); } -class nsAsyncNotifyGlyphMetricsChange MOZ_FINAL : public nsIReflowCallback -{ -public: - nsAsyncNotifyGlyphMetricsChange(nsIFrame* aFrame) : mWeakFrame(aFrame) - { - } - - virtual bool ReflowFinished() - { - nsSVGTextPathFrame* frame = - static_cast(mWeakFrame.GetFrame()); - if (frame) { - frame->NotifyGlyphMetricsChange(); - } - delete this; - return true; - } - - virtual void ReflowCallbackCanceled() - { - delete this; - } - - nsWeakFrame mWeakFrame; -}; - void nsSVGTextPathProperty::DoUpdate() { @@ -322,13 +296,11 @@ nsSVGTextPathProperty::DoUpdate() NS_ASSERTION(mFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); if (mFrame->GetType() == nsGkAtoms::svgTextPathFrame) { - if (mFrame->PresContext()->PresShell()->IsReflowLocked()) { - nsIReflowCallback* cb = new nsAsyncNotifyGlyphMetricsChange(mFrame); - mFrame->PresContext()->PresShell()->PostReflowCallback(cb); - } else { - nsSVGTextPathFrame* textPathFrame = static_cast(mFrame); - textPathFrame->NotifyGlyphMetricsChange(); - } + // Repaint asynchronously in case the path frame is being torn down + nsChangeHint changeHint = + nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_UpdateTextPath); + mFramePresShell->FrameConstructor()->PostRestyleEvent( + mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); } } From b9ac8afd0894274370e8997511e10b01c327f21d Mon Sep 17 00:00:00 2001 From: Ed Morley Date: Wed, 7 Nov 2012 10:47:27 +0000 Subject: [PATCH 12/77] Backout e89f1fce980d (bug 509052) for an assortment of timeouts and crashes --- gfx/2d/Blur.cpp | 254 ++++-------------------------------- gfx/2d/Blur.h | 8 -- gfx/2d/BlurSSE2.cpp | 250 ----------------------------------- gfx/2d/ImageScalingSSE2.cpp | 14 +- gfx/2d/Makefile.in | 7 +- gfx/2d/SSEHelpers.h | 17 --- gfx/2d/Tools.h | 58 -------- 7 files changed, 43 insertions(+), 565 deletions(-) delete mode 100644 gfx/2d/BlurSSE2.cpp delete mode 100644 gfx/2d/SSEHelpers.h diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp index d1f5c9616e9..8def843f3f4 100644 --- a/gfx/2d/Blur.cpp +++ b/gfx/2d/Blur.cpp @@ -12,9 +12,6 @@ #include "mozilla/Constants.h" #include "mozilla/Util.h" -#include "2D.h" -#include "Tools.h" - using namespace std; namespace mozilla { @@ -314,8 +311,8 @@ SpreadVertical(unsigned char* aInput, } } -CheckedInt -AlphaBoxBlur::RoundUpToMultipleOf4(int32_t aVal) +static CheckedInt +RoundUpToMultipleOf4(int32_t aVal) { CheckedInt val(aVal); @@ -381,9 +378,10 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, if (stride.isValid()) { mStride = stride.value(); - CheckedInt size = CheckedInt(mStride) * mRect.height; + CheckedInt size = CheckedInt(mStride) * mRect.height * + sizeof(unsigned char); if (size.isValid()) { - mData = new uint8_t[size.value()]; + mData = static_cast(malloc(size.value())); memset(mData, 0, size.value()); } } @@ -407,7 +405,7 @@ AlphaBoxBlur::AlphaBoxBlur(uint8_t* aData, AlphaBoxBlur::~AlphaBoxBlur() { if (mFreeData) { - delete [] mData; + free(mData); } } @@ -457,236 +455,42 @@ AlphaBoxBlur::Blur() if (mBlurRadius != IntSize(0,0) || mSpreadRadius != IntSize(0,0)) { int32_t stride = GetStride(); - IntSize size = GetSize(); + // No need to use CheckedInt here - we have validated it in the constructor. + size_t szB = stride * GetSize().height * sizeof(unsigned char); + unsigned char* tmpData = static_cast(malloc(szB)); + if (!tmpData) + return; // OOM + + memset(tmpData, 0, szB); if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) { - // No need to use CheckedInt here - we have validated it in the constructor. - size_t szB = stride * size.height; - unsigned char* tmpData = new uint8_t[szB]; - - memset(tmpData, 0, szB); - SpreadHorizontal(mData, tmpData, mSpreadRadius.width, GetSize().width, GetSize().height, stride, mSkipRect); SpreadVertical(tmpData, mData, mSpreadRadius.height, GetSize().width, GetSize().height, stride, mSkipRect); - - delete [] tmpData; } - int32_t horizontalLobes[3][2]; - ComputeLobes(mBlurRadius.width, horizontalLobes); - int32_t verticalLobes[3][2]; - ComputeLobes(mBlurRadius.height, verticalLobes); - - // We want to allow for some extra space on the left for alignment reasons. - int32_t maxLeftLobe = RoundUpToMultipleOf4(horizontalLobes[0][0] + 1).value(); - - IntSize integralImageSize(size.width + maxLeftLobe + horizontalLobes[1][1], - size.height + verticalLobes[0][0] + verticalLobes[1][1] + 1); - -#ifdef IS_BIG_ENDIAN - const bool cIsBigEndian = true; -#else - const bool cIsBigEndian = false; -#endif - - if (cIsBigEndian || (integralImageSize.width * integralImageSize.height) > (1 << 24)) { - // Fallback to old blurring code when the surface is so large it may - // overflow our integral image! - - // No need to use CheckedInt here - we have validated it in the constructor. - size_t szB = stride * size.height; - unsigned char* tmpData = new uint8_t[szB]; - - memset(tmpData, 0, szB); - - if (mBlurRadius.width > 0) { - BoxBlurHorizontal(mData, tmpData, horizontalLobes[0][0], horizontalLobes[0][1], stride, GetSize().height, mSkipRect); - BoxBlurHorizontal(tmpData, mData, horizontalLobes[1][0], horizontalLobes[1][1], stride, GetSize().height, mSkipRect); - BoxBlurHorizontal(mData, tmpData, horizontalLobes[2][0], horizontalLobes[2][1], stride, GetSize().height, mSkipRect); - } else { - uint8_t *tmp = mData; - mData = tmpData; - tmpData = tmp; - } - if (mBlurRadius.height > 0) { - BoxBlurVertical(tmpData, mData, verticalLobes[0][0], verticalLobes[0][1], stride, GetSize().height, mSkipRect); - BoxBlurVertical(mData, tmpData, verticalLobes[1][0], verticalLobes[1][1], stride, GetSize().height, mSkipRect); - BoxBlurVertical(tmpData, mData, verticalLobes[2][0], verticalLobes[2][1], stride, GetSize().height, mSkipRect); - } else { - uint8_t *tmp = mData; - mData = tmpData; - tmpData = tmp; - } - - delete [] tmpData; + if (mBlurRadius.width > 0) { + int32_t lobes[3][2]; + ComputeLobes(mBlurRadius.width, lobes); + BoxBlurHorizontal(mData, tmpData, lobes[0][0], lobes[0][1], stride, GetSize().height, mSkipRect); + BoxBlurHorizontal(tmpData, mData, lobes[1][0], lobes[1][1], stride, GetSize().height, mSkipRect); + BoxBlurHorizontal(mData, tmpData, lobes[2][0], lobes[2][1], stride, GetSize().height, mSkipRect); } else { - size_t integralImageStride = GetAlignedStride<16>(integralImageSize.width * 4); - - AlignedArray integralImage((integralImageStride / 4) * integralImageSize.height); - -#ifdef USE_SSE2 - if (Factory::HasSSE2()) { - BoxBlur_SSE2(horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], - verticalLobes[0][1], integralImage, integralImageStride); - BoxBlur_SSE2(horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], - verticalLobes[1][1], integralImage, integralImageStride); - BoxBlur_SSE2(horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], - verticalLobes[2][1], integralImage, integralImageStride); - } else -#endif - { - BoxBlur_C(horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], - verticalLobes[0][1], integralImage, integralImageStride); - BoxBlur_C(horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], - verticalLobes[1][1], integralImage, integralImageStride); - BoxBlur_C(horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], - verticalLobes[2][1], integralImage, integralImageStride); - } + memcpy(tmpData, mData, stride * GetSize().height); } - } -} -MOZ_ALWAYS_INLINE void -GenerateIntegralRow(uint32_t *aDest, const uint8_t *aSource, uint32_t *aPreviousRow, - const uint32_t &aSourceWidth, const uint32_t &aLeftInflation, const uint32_t &aRightInflation) -{ - uint32_t currentRowSum = 0; - uint32_t pixel = aSource[0]; - for (uint32_t x = 0; x < aLeftInflation; x++) { - currentRowSum += pixel; - *aDest++ = currentRowSum + *aPreviousRow++; - } - for (uint32_t x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x += 4) { - uint32_t alphaValues = *(uint32_t*)(aSource + (x - aLeftInflation)); - currentRowSum += alphaValues & 0xff; - *aDest++ = *aPreviousRow++ + currentRowSum; - alphaValues >>= 8; - currentRowSum += alphaValues & 0xff; - *aDest++ = *aPreviousRow++ + currentRowSum; - alphaValues >>= 8; - currentRowSum += alphaValues & 0xff; - *aDest++ = *aPreviousRow++ + currentRowSum; - alphaValues >>= 8; - currentRowSum += alphaValues & 0xff; - *aDest++ = *aPreviousRow++ + currentRowSum; - } - pixel = aSource[aSourceWidth - 1]; - for (uint32_t x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { - currentRowSum += pixel; - *aDest++ = currentRowSum + *aPreviousRow++; - } -} - -MOZ_ALWAYS_INLINE void -GenerateIntegralImage_C(int32_t aLeftInflation, int32_t aRightInflation, - int32_t aTopInflation, int32_t aBottomInflation, - uint32_t *aIntegralImage, size_t aIntegralImageStride, - uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) -{ - uint32_t stride32bit = aIntegralImageStride / 4; - - IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, - aSize.height + aTopInflation + aBottomInflation); - - memset(aIntegralImage, 0, aIntegralImageStride); - - GenerateIntegralRow(aIntegralImage, aSource, aIntegralImage, - aSize.width, aLeftInflation, aRightInflation); - for (int y = 1; y < aTopInflation + 1; y++) { - uint32_t *intRow = aIntegralImage + (y * stride32bit); - uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; - uint32_t *intFirstRow = aIntegralImage; - - GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource, aIntegralImage + (y - 1) * stride32bit, - aSize.width, aLeftInflation, aRightInflation); - } - - for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { - GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource + aSourceStride * (y - aTopInflation), - aIntegralImage + (y - 1) * stride32bit, aSize.width, aLeftInflation, aRightInflation); - } - - if (aBottomInflation) { - for (int y = (aSize.height + aTopInflation); y < integralImageSize.height; y++) { - GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource + ((aSize.height - 1) * aSourceStride), - aIntegralImage + (y - 1) * stride32bit, - aSize.width, aLeftInflation, aRightInflation); + if (mBlurRadius.height > 0) { + int32_t lobes[3][2]; + ComputeLobes(mBlurRadius.height, lobes); + BoxBlurVertical(tmpData, mData, lobes[0][0], lobes[0][1], stride, GetSize().height, mSkipRect); + BoxBlurVertical(mData, tmpData, lobes[1][0], lobes[1][1], stride, GetSize().height, mSkipRect); + BoxBlurVertical(tmpData, mData, lobes[2][0], lobes[2][1], stride, GetSize().height, mSkipRect); + } else { + memcpy(mData, tmpData, stride * GetSize().height); } - } -} -/** - * Attempt to do an in-place box blur using an integral image. - */ -void -AlphaBoxBlur::BoxBlur_C(int32_t aLeftLobe, - int32_t aRightLobe, - int32_t aTopLobe, - int32_t aBottomLobe, - uint32_t *aIntegralImage, - size_t aIntegralImageStride) -{ - IntSize size = GetSize(); - - MOZ_ASSERT(size.width > 0); - - // Our 'left' or 'top' lobe will include the current pixel. i.e. when - // looking at an integral image the value of a pixel at 'x,y' is calculated - // using the value of the integral image values above/below that. - aLeftLobe++; - aTopLobe++; - int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); - - MOZ_ASSERT(boxSize > 0); - - if (boxSize == 1) { - return; + free(tmpData); } - uint32_t stride32bit = aIntegralImageStride / 4; - - int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); - - GenerateIntegralImage_C(leftInflation, aRightLobe, aTopLobe, aBottomLobe, - aIntegralImage, aIntegralImageStride, mData, - mStride, size); - - uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); - - uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; - - // Storing these locally makes this about 30% faster! Presumably the compiler - // can't be sure we're not altering the member variables in this loop. - IntRect skipRect = mSkipRect; - uint8_t *data = mData; - int32_t stride = mStride; - for (int32_t y = 0; y < size.height; y++) { - bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); - - uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * stride32bit - aLeftLobe); - uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * stride32bit + aRightLobe); - uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * stride32bit + aRightLobe); - uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * stride32bit - aLeftLobe); - - for (int32_t x = 0; x < size.width; x++) { - if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { - x = skipRect.XMost() - 1; - // Trigger early jump on coming loop iterations, this will be reset - // next line anyway. - inSkipRectY = false; - continue; - } - int32_t topLeft = topLeftBase[x]; - int32_t topRight = topRightBase[x]; - int32_t bottomRight = bottomRightBase[x]; - int32_t bottomLeft = bottomLeftBase[x]; - - uint32_t value = bottomRight - topRight - bottomLeft; - value += topLeft; - - data[stride * y + x] = (uint64_t(reciprocal) * value) >> 32; - } - } } /** diff --git a/gfx/2d/Blur.h b/gfx/2d/Blur.h index d425d6ee9f2..24f32941904 100644 --- a/gfx/2d/Blur.h +++ b/gfx/2d/Blur.h @@ -7,7 +7,6 @@ #include "mozilla/gfx/Rect.h" #include "mozilla/gfx/Point.h" -#include "mozilla/CheckedInt.h" namespace mozilla { namespace gfx { @@ -115,13 +114,6 @@ public: private: - void BoxBlur_C(int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, - int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); - void BoxBlur_SSE2(int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, - int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); - - static CheckedInt RoundUpToMultipleOf4(int32_t aVal); - /** * A rect indicating the area where blurring is unnecessary, and the blur * algorithm should skip over it. diff --git a/gfx/2d/BlurSSE2.cpp b/gfx/2d/BlurSSE2.cpp deleted file mode 100644 index d53153d49be..00000000000 --- a/gfx/2d/BlurSSE2.cpp +++ /dev/null @@ -1,250 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "Blur.h" - -#include "SSEHelpers.h" - -#include - -namespace mozilla { -namespace gfx { - -MOZ_ALWAYS_INLINE -uint32_t DivideAndPack(__m128i aValues, __m128i aDivisor, __m128i aMask) -{ - __m128i multiplied = _mm_srli_epi64(_mm_mul_epu32(aValues, aDivisor), 32); // 00p300p1 - multiplied = _mm_or_si128(multiplied, _mm_and_si128(_mm_mul_epu32(_mm_srli_epi64(aValues, 32), aDivisor), - aMask)); // p4p3p2p1 - __m128i final = _mm_packus_epi16(_mm_packs_epi32(multiplied, _mm_setzero_si128()), _mm_setzero_si128()); - - return _mm_cvtsi128_si32(final); -} - -MOZ_ALWAYS_INLINE -void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource, - int32_t aSourceWidth, int32_t aLeftInflation, - int32_t aRightInflation) -{ - int32_t currentRowSum = 0; - - for (int x = 0; x < aLeftInflation; x++) { - currentRowSum += aSource[0]; - aDest[x] = currentRowSum; - } - for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { - currentRowSum += aSource[(x - aLeftInflation)]; - aDest[x] = currentRowSum; - } - for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { - currentRowSum += aSource[aSourceWidth - 1]; - aDest[x] = currentRowSum; - } -} - -// This function calculates an integral of four pixels stored in the 4 -// 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns -// { 30, 80, 160, 260 }. This seems to be the fastest way to do this after -// much testing. -MOZ_ALWAYS_INLINE -__m128i AccumulatePixelSums(__m128i aPixels) -{ - __m128i sumPixels = aPixels; - __m128i currentPixels = _mm_slli_si128(aPixels, 4); - sumPixels = _mm_add_epi32(sumPixels, currentPixels); - currentPixels = _mm_unpacklo_epi64(_mm_setzero_si128(), sumPixels); - - return _mm_add_epi32(sumPixels, currentPixels); -} - -MOZ_ALWAYS_INLINE void -GenerateIntegralImage_SSE2(int32_t aLeftInflation, int32_t aRightInflation, - int32_t aTopInflation, int32_t aBottomInflation, - uint32_t *aIntegralImage, size_t aIntegralImageStride, - uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) -{ - MOZ_ASSERT(!(aLeftInflation & 3)); - - uint32_t stride32bit = aIntegralImageStride / 4; - - IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, - aSize.height + aTopInflation + aBottomInflation); - - LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation); - - for (int y = 1; y < aTopInflation + 1; y++) { - uint32_t *intRow = aIntegralImage + (y * stride32bit); - uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; - uint32_t *intFirstRow = aIntegralImage; - - for (int x = 0; x < integralImageSize.width; x += 4) { - __m128i firstRow = _mm_load_si128((__m128i*)(intFirstRow + x)); - __m128i previousRow = _mm_load_si128((__m128i*)(intPrevRow + x)); - _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(firstRow, previousRow)); - } - } - - for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { - __m128i currentRowSum = _mm_setzero_si128(); - uint32_t *intRow = aIntegralImage + (y * stride32bit); - uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; - uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation); - - uint32_t pixel = sourceRow[0]; - for (int x = 0; x < aLeftInflation; x += 4) { - __m128i sumPixels = AccumulatePixelSums(_mm_shuffle_epi32(_mm_set1_epi32(pixel), _MM_SHUFFLE(0, 0, 0, 0))); - - sumPixels = _mm_add_epi32(sumPixels, currentRowSum); - - currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); - - _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); - } - for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { - uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation)); - - // It's important to shuffle here. When we exit this loop currentRowSum - // has to be set to sumPixels, so that the following loop can get the - // correct pixel for the currentRowSum. The highest order pixel in - // currentRowSum could've originated from accumulation in the stride. - currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); - - __m128i sumPixels = AccumulatePixelSums(_mm_unpacklo_epi16(_mm_unpacklo_epi8( _mm_set1_epi32(pixels), _mm_setzero_si128()), _mm_setzero_si128())); - sumPixels = _mm_add_epi32(sumPixels, currentRowSum); - - currentRowSum = sumPixels; - - _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); - } - - pixel = sourceRow[aSize.width - 1]; - int x = (aSize.width + aLeftInflation); - if ((aSize.width & 3)) { - // Deal with unaligned portion. Get the correct pixel from currentRowSum, - // see explanation above. - uint32_t intCurrentRowSum = ((uint32_t*)¤tRowSum)[(aSize.width % 4) - 1]; - for (; x < integralImageSize.width; x++) { - // We could be unaligned here! - if (!(x & 3)) { - // aligned! - currentRowSum = _mm_set1_epi32(intCurrentRowSum); - break; - } - intCurrentRowSum += pixel; - intRow[x] = intPrevRow[x] + intCurrentRowSum; - } - } else { - currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); - } - for (; x < integralImageSize.width; x += 4) { - __m128i sumPixels = AccumulatePixelSums(_mm_set1_epi32(pixel)); - - sumPixels = _mm_add_epi32(sumPixels, currentRowSum); - - currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); - - _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); - } - } - - if (aBottomInflation) { - // Store the last valid row of our source image in the last row of - // our integral image. This will be overwritten with the correct values - // in the upcoming loop. - LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit, - aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation); - - - for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) { - __m128i *intRow = (__m128i*)(aIntegralImage + (y * stride32bit)); - __m128i *intPrevRow = (__m128i*)(aIntegralImage + (y - 1) * stride32bit); - __m128i *intLastRow = (__m128i*)(aIntegralImage + (integralImageSize.height - 1) * stride32bit); - - for (int x = 0; x < integralImageSize.width; x += 4) { - _mm_store_si128(intRow + (x / 4), - _mm_add_epi32(_mm_load_si128(intLastRow + (x / 4)), - _mm_load_si128(intPrevRow + (x / 4)))); - } - } - } -} - -/** - * Attempt to do an in-place box blur using an integral image. - */ -void -AlphaBoxBlur::BoxBlur_SSE2(int32_t aLeftLobe, - int32_t aRightLobe, - int32_t aTopLobe, - int32_t aBottomLobe, - uint32_t *aIntegralImage, - size_t aIntegralImageStride) -{ - IntSize size = GetSize(); - - MOZ_ASSERT(size.height > 0); - - // Our 'left' or 'top' lobe will include the current pixel. i.e. when - // looking at an integral image the value of a pixel at 'x,y' is calculated - // using the value of the integral image values above/below that. - aLeftLobe++; - aTopLobe++; - int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); - - MOZ_ASSERT(boxSize > 0); - - if (boxSize == 1) { - return; - } - - uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); - - uint32_t stride32bit = aIntegralImageStride / 4; - int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); - - GenerateIntegralImage_SSE2(leftInflation, aRightLobe, aTopLobe, aBottomLobe, - aIntegralImage, aIntegralImageStride, mData, - mStride, size); - - __m128i divisor = _mm_set1_epi32(reciprocal); - __m128i mask = _mm_setr_epi32(0x0, 0xffffffff, 0x0, 0xffffffff); - - // This points to the start of the rectangle within the IntegralImage that overlaps - // the surface being blurred. - uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; - - IntRect skipRect = mSkipRect; - int32_t stride = mStride; - uint8_t *data = mData; - for (int32_t y = 0; y < size.height; y++) { - bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); - - uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); - uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); - uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); - uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); - - for (int32_t x = 0; x < size.width; x += 4) { - if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { - x = skipRect.XMost() - 4; - // Trigger early jump on coming loop iterations, this will be reset - // next line anyway. - inSkipRectY = false; - continue; - } - __m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); - __m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x)); - __m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); - __m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); - - __m128i values = _mm_add_epi32(_mm_sub_epi32(_mm_sub_epi32(bottomRight, topRight), bottomLeft), topLeft); - - *(uint32_t*)(data + stride * y + x) = DivideAndPack(values, divisor, mask); - } - } - -} - -} -} diff --git a/gfx/2d/ImageScalingSSE2.cpp b/gfx/2d/ImageScalingSSE2.cpp index f87653b4cab..cad5eb85aae 100644 --- a/gfx/2d/ImageScalingSSE2.cpp +++ b/gfx/2d/ImageScalingSSE2.cpp @@ -6,7 +6,8 @@ #include "ImageScaling.h" #include "mozilla/Attributes.h" -#include "SSEHelpers.h" +#include +#include /* The functions below use the following system for averaging 4 pixels: * @@ -107,6 +108,17 @@ MOZ_ALWAYS_INLINE __m128i avg_sse2_8x1_4x1(__m128i a, __m128i b) return _mm_not_si128(_mm_avg_epu8(_mm_not_si128(a), _mm_not_si128(b))); } +/* Before Nehalem _mm_loadu_si128 could be very slow, this trick is a little + * faster. Once enough people are on architectures where _mm_loadu_si128 is + * fast we can migrate to it. + */ +MOZ_ALWAYS_INLINE __m128i loadUnaligned128(const __m128i *aSource) +{ + // Yes! We use uninitialized memory here, we'll overwrite it though! + __m128 res = _mm_loadl_pi(_mm_set1_ps(0), (const __m64*)aSource); + return _mm_castps_si128(_mm_loadh_pi(res, ((const __m64*)(aSource)) + 1)); +} + MOZ_ALWAYS_INLINE uint32_t Avg2x2(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { uint32_t sum = a ^ b ^ c; diff --git a/gfx/2d/Makefile.in b/gfx/2d/Makefile.in index 376f72cfb90..6959f5d2c32 100644 --- a/gfx/2d/Makefile.in +++ b/gfx/2d/Makefile.in @@ -116,10 +116,7 @@ endif ifneq (,$(INTEL_ARCHITECTURE)) # VC2005 doesn't support _mm_castsi128_ps, so SSE2 is turned off ifneq (1400,$(_MSC_VER)) -CPPSRCS += \ - ImageScalingSSE2.cpp \ - BlurSSE2.cpp \ - $(NULL) +CPPSRCS += ImageScalingSSE2.cpp DEFINES += -DUSE_SSE2 endif endif @@ -164,12 +161,10 @@ DEFINES := $(filter-out -DUNICODE -D_UNICODE,$(DEFINES)) ifneq (,$(INTEL_ARCHITECTURE)) ifdef GNU_CC ImageScalingSSE2.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 -BlurSSE2.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 endif ifdef SOLARIS_SUNPRO_CXX ImageScalingSSE2.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 -BlurSSE2.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 endif endif diff --git a/gfx/2d/SSEHelpers.h b/gfx/2d/SSEHelpers.h deleted file mode 100644 index 61b53d86eac..00000000000 --- a/gfx/2d/SSEHelpers.h +++ /dev/null @@ -1,17 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include -#include - -/* Before Nehalem _mm_loadu_si128 could be very slow, this trick is a little - * faster. Once enough people are on architectures where _mm_loadu_si128 is - * fast we can migrate to it. - */ -MOZ_ALWAYS_INLINE __m128i loadUnaligned128(const __m128i *aSource) -{ - // Yes! We use uninitialized memory here, we'll overwrite it though! - __m128 res = _mm_loadl_pi(_mm_set1_ps(0), (const __m64*)aSource); - return _mm_castps_si128(_mm_loadh_pi(res, ((const __m64*)(aSource)) + 1)); -} diff --git a/gfx/2d/Tools.h b/gfx/2d/Tools.h index 60e89fa0ea2..d645c2b219d 100644 --- a/gfx/2d/Tools.h +++ b/gfx/2d/Tools.h @@ -81,64 +81,6 @@ BytesPerPixel(SurfaceFormat aFormat) } } -template -struct AlignedArray -{ - AlignedArray() - : mStorage(nullptr) - , mPtr(nullptr) - { - } - - MOZ_ALWAYS_INLINE AlignedArray(size_t aSize) - : mStorage(nullptr) - { - Realloc(aSize); - } - - MOZ_ALWAYS_INLINE ~AlignedArray() - { - delete [] mStorage; - } - - void Dealloc() - { - delete [] mStorage; - mStorage = mPtr = nullptr; - } - - MOZ_ALWAYS_INLINE void Realloc(size_t aSize) - { - delete [] mStorage; - mStorage = new T[aSize + (alignment - 1)]; - if (uintptr_t(mStorage) % alignment) { - // Our storage does not start at a -byte boundary. Make sure mData does! - mPtr = (uint32_t*)(uintptr_t(mStorage) + - (alignment - (uintptr_t(mStorage) % alignment))); - } else { - mPtr = mStorage; - } - } - - MOZ_ALWAYS_INLINE operator T*() - { - return mPtr; - } - - T *mStorage; - T *mPtr; -}; - -template -int32_t GetAlignedStride(int32_t aStride) -{ - if (aStride % alignment) { - return aStride + (alignment - (aStride % alignment)); - } - - return aStride; -} - } } From 962591ca72b7345b20c77111aa752704f93014c5 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 7 Nov 2012 12:53:24 +0100 Subject: [PATCH 13/77] Bug 802073 - Receive input event twice from input tag type:time and type:date r=vingtetun a=blocking-basecamp --- b2g/chrome/content/forms.js | 131 +++++++++++++++++++--------------- b2g/chrome/content/shell.js | 1 + b2g/components/Keyboard.jsm | 101 ++++++++++++++++++++++++++ b2g/components/Makefile.in | 1 + b2g/components/MozKeyboard.js | 57 +++++---------- 5 files changed, 193 insertions(+), 98 deletions(-) create mode 100644 b2g/components/Keyboard.jsm diff --git a/b2g/chrome/content/forms.js b/b2g/chrome/content/forms.js index 95150ef3c8b..6f8e090f2d4 100644 --- a/b2g/chrome/content/forms.js +++ b/b2g/chrome/content/forms.js @@ -18,6 +18,13 @@ XPCOMUtils.defineLazyServiceGetter(Services, "fm", "@mozilla.org/focus-manager;1", "nsIFocusManager"); +XPCOMUtils.defineLazyGetter(this, "domWindowUtils", function () { + return content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); +}); + +const FOCUS_CHANGE_DELAY = 20; + let HTMLInputElement = Ci.nsIDOMHTMLInputElement; let HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement; let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement; @@ -28,7 +35,6 @@ let FormAssistant = { init: function fa_init() { addEventListener("focus", this, true, false); addEventListener("blur", this, true, false); - addEventListener("keypress", this, true, false); addEventListener("resize", this, true, false); addMessageListener("Forms:Select:Choice", this); addMessageListener("Forms:Input:Value", this); @@ -37,6 +43,10 @@ let FormAssistant = { Services.obs.addObserver(this, "xpcom-shutdown", false); }, + ignoredInputTypes: new Set([ + 'button', 'file', 'checkbox', 'radio', 'reset', 'submit', 'image' + ]), + isKeyboardOpened: false, selectionStart: 0, selectionEnd: 0, @@ -80,43 +90,24 @@ let FormAssistant = { switch (evt.type) { case "focus": - if (this.isKeyboardOpened) + if (this.isIMEDisabled()) return; - let ignore = { - button: true, - file: true, - checkbox: true, - radio: true, - reset: true, - submit: true, - image: true - }; - - if (target instanceof HTMLSelectElement) { - content.setTimeout(function showIMEForSelect() { - sendAsyncMessage("Forms:Input", getJSON(target)); - }); - this.setFocusedElement(target); - } else if (target instanceof HTMLOptionElement && - target.parentNode instanceof HTMLSelectElement) { - target = target.parentNode; - content.setTimeout(function showIMEForSelect() { - sendAsyncMessage("Forms:Input", getJSON(target)); - }); - this.setFocusedElement(target); - } else if ((target instanceof HTMLInputElement && !ignore[target.type]) || - target instanceof HTMLTextAreaElement) { - this.isKeyboardOpened = this.tryShowIme(target); - this.setFocusedElement(target); + if (target && this.isFocusableElement(target)) { + if (this.blurTimeout) { + this.blurTimeout = content.clearTimeout(this.blurTimeout); + this.handleIMEStateDisabled(); + } + this.handleIMEStateEnabled(target); } break; case "blur": if (this.focusedElement) { - sendAsyncMessage("Forms:Input", { "type": "blur" }); - this.setFocusedElement(null); - this.isKeyboardOpened = false; + this.blurTimeout = content.setTimeout(function () { + this.blurTimeout = null; + this.handleIMEStateDisabled(); + }.bind(this), FOCUS_CHANGE_DELAY); } break; @@ -146,17 +137,6 @@ let FormAssistant = { this.focusedElement.scrollIntoView(false); } break; - - case "keypress": - if (evt.keyCode != evt.DOM_VK_ESCAPE || !this.isKeyboardOpened) - return; - - sendAsyncMessage("Forms:Input", { "type": "blur" }); - this.isKeyboardOpened = false; - - evt.preventDefault(); - evt.stopPropagation(); - break; } }, @@ -213,21 +193,17 @@ let FormAssistant = { observe: function fa_observe(subject, topic, data) { switch (topic) { case "ime-enabled-state-changed": - let isOpen = this.isKeyboardOpened; let shouldOpen = parseInt(data); - if (shouldOpen && !isOpen) { - let target = Services.fm.focusedElement; + let target = Services.fm.focusedElement; + if (!target) + return; - if (!target || !this.tryShowIme(target)) { - this.setFocusedElement(null); - return; - } else { - this.setFocusedElement(target); - } - } else if (!shouldOpen && isOpen) { - sendAsyncMessage("Forms:Input", { "type": "blur" }); + if (shouldOpen) { + if (!this.focusedElement && this.isFocusableElement(target)) + this.handleIMEStateEnabled(target); + } else if (this._focusedElement == target) { + this.handleIMEStateDisabled(); } - this.isKeyboardOpened = shouldOpen; break; case "xpcom-shutdown": @@ -239,11 +215,50 @@ let FormAssistant = { } }, - tryShowIme: function(element) { - if (!element) { - return; - } + isIMEDisabled: function fa_isIMEDisabled() { + let disabled = false; + try { + disabled = domWindowUtils.IMEStatus == domWindowUtils.IME_STATUS_DISABLED; + } catch (e) {} + return disabled; + }, + + handleIMEStateEnabled: function fa_handleIMEStateEnabled(target) { + if (this.isKeyboardOpened) + return; + + if (target instanceof HTMLOptionElement) + target = target.parentNode; + + let kbOpened = this.tryShowIme(target); + if (target instanceof HTMLInputElement || + target instanceof HTMLTextAreaElement) + this.isKeyboardOpened = kbOpened; + + this.setFocusedElement(target); + }, + + handleIMEStateDisabled: function fa_handleIMEStateDisabled() { + sendAsyncMessage("Forms:Input", { "type": "blur" }); + this.isKeyboardOpened = false; + this.setFocusedElement(null); + }, + + isFocusableElement: function fa_isFocusableElement(element) { + if (element instanceof HTMLSelectElement || + element instanceof HTMLTextAreaElement) + return true; + + if (element instanceof HTMLOptionElement && + element.parentNode instanceof HTMLSelectElement) + return true; + + return (element instanceof HTMLInputElement && + !this.ignoredInputTypes.has(element.type)); + }, + + tryShowIme: function(element) { // FIXME/bug 729623: work around apparent bug in the IME manager // in gecko. let readonly = element.getAttribute("readonly"); diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index 127e2ea6340..631d49eb183 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -17,6 +17,7 @@ Cu.import('resource://gre/modules/accessibility/AccessFu.jsm'); Cu.import('resource://gre/modules/Payment.jsm'); Cu.import("resource://gre/modules/AppsUtils.jsm"); Cu.import('resource://gre/modules/UserAgentOverrides.jsm'); +Cu.import('resource://gre/modules/Keyboard.jsm'); #ifdef MOZ_B2G_RIL Cu.import('resource://gre/modules/NetworkStatsService.jsm'); #endif diff --git a/b2g/components/Keyboard.jsm b/b2g/components/Keyboard.jsm new file mode 100644 index 00000000000..316eac6d635 --- /dev/null +++ b/b2g/components/Keyboard.jsm @@ -0,0 +1,101 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +this.EXPORTED_SYMBOLS = ['Keyboard']; + +const Cu = Components.utils; +const Cc = Components.classes; +const Ci = Components.interfaces; +const kFormsFrameScript = 'chrome://browser/content/forms.js'; + +Cu.import('resource://gre/modules/Services.jsm'); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "ppmm", + "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster"); + +let Keyboard = { + _messageManager: null, + _messageNames: [ + 'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions' + ], + + get messageManager() { + if (this._messageManager && !Cu.isDeadWrapper(this._messageManager)) + return this._messageManager; + + throw Error('no message manager set'); + }, + + set messageManager(mm) { + this._messageManager = mm; + }, + + init: function keyboardInit() { + Services.obs.addObserver(this, 'in-process-browser-frame-shown', false); + Services.obs.addObserver(this, 'remote-browser-frame-shown', false); + + for (let name of this._messageNames) + ppmm.addMessageListener('Keyboard:' + name, this); + }, + + observe: function keyboardObserve(subject, topic, data) { + let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader); + let mm = frameLoader.messageManager; + mm.addMessageListener('Forms:Input', this); + + try { + mm.loadFrameScript(kFormsFrameScript, true); + } catch (e) { + dump('Error loading ' + kFormsFrameScript + ' as frame script: ' + e + '\n'); + } + }, + + receiveMessage: function keyboardReceiveMessage(msg) { + switch (msg.name) { + case 'Forms:Input': + this.handleFormsInput(msg); + break; + case 'Keyboard:SetValue': + this.setValue(msg); + break; + case 'Keyboard:RemoveFocus': + this.removeFocus(); + break; + case 'Keyboard:SetSelectedOption': + this.setSelectedOption(msg); + break; + case 'Keyboard:SetSelectedOptions': + this.setSelectedOption(msg); + break; + } + }, + + handleFormsInput: function keyboardHandleFormsInput(msg) { + this.messageManager = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner) + .frameLoader.messageManager; + + ppmm.broadcastAsyncMessage('Keyboard:FocusChange', msg.data); + }, + + setSelectedOption: function keyboardSetSelectedOption(msg) { + this.messageManager.sendAsyncMessage('Forms:Select:Choice', msg.data); + }, + + setSelectedOptions: function keyboardSetSelectedOptions(msg) { + this.messageManager.sendAsyncMessage('Forms:Select:Choice', msg.data); + }, + + setValue: function keyboardSetValue(msg) { + this.messageManager.sendAsyncMessage('Forms:Input:Value', msg.data); + }, + + removeFocus: function keyboardRemoveFocus() { + this.messageManager.sendAsyncMessage('Forms:Select:Blur', {}); + } +}; + +Keyboard.init(); diff --git a/b2g/components/Makefile.in b/b2g/components/Makefile.in index dc244bc0bb3..d8453a2bfcc 100644 --- a/b2g/components/Makefile.in +++ b/b2g/components/Makefile.in @@ -34,6 +34,7 @@ EXTRA_PP_COMPONENTS = \ $(NULL) EXTRA_JS_MODULES = \ + Keyboard.jsm \ TelURIParser.jsm \ $(NULL) diff --git a/b2g/components/MozKeyboard.js b/b2g/components/MozKeyboard.js index 87146ba628b..16613d0f475 100644 --- a/b2g/components/MozKeyboard.js +++ b/b2g/components/MozKeyboard.js @@ -4,15 +4,16 @@ "use strict"; -const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; -const kFormsFrameScript = "chrome://browser/content/forms.js"; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/ObjectWrapper.jsm"); +XPCOMUtils.defineLazyServiceGetter(this, "cpmm", + "@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender"); + // ----------------------------------------------------------------------- // MozKeyboard // ----------------------------------------------------------------------- @@ -36,20 +37,19 @@ MozKeyboard.prototype = { init: function mozKeyboardInit(win) { Services.obs.addObserver(this, "inner-window-destroyed", false); - Services.obs.addObserver(this, 'in-process-browser-frame-shown', false); - Services.obs.addObserver(this, 'remote-browser-frame-shown', false); + cpmm.addMessageListener('Keyboard:FocusChange', this); this._window = win; this._utils = win.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); this.innerWindowID = this._utils.currentInnerWindowID; - this._focusHandler = null; }, uninit: function mozKeyboardUninit() { Services.obs.removeObserver(this, "inner-window-destroyed"); - this._messageManager = null; + cpmm.removeMessageListener('Keyboard:FocusChange', this); + this._window = null; this._utils = null; this._focusHandler = null; @@ -63,25 +63,25 @@ MozKeyboard.prototype = { }, setSelectedOption: function mozKeyboardSetSelectedOption(index) { - this._messageManager.sendAsyncMessage("Forms:Select:Choice", { - "index": index + cpmm.sendAsyncMessage('Keyboard:SetSelectedOption', { + 'index': index }); }, setValue: function mozKeyboardSetValue(value) { - this._messageManager.sendAsyncMessage("Forms:Input:Value", { - "value": value + cpmm.sendAsyncMessage('Keyboard:SetValue', { + 'value': value }); }, setSelectedOptions: function mozKeyboardSetSelectedOptions(indexes) { - this._messageManager.sendAsyncMessage("Forms:Select:Choice", { - "indexes": indexes || [] + cpmm.sendAsyncMessage('Keyboard:SetSelectedOptions', { + 'indexes': indexes }); }, removeFocus: function mozKeyboardRemoveFocus() { - this._messageManager.sendAsyncMessage("Forms:Select:Blur", {}); + cpmm.sendAsyncMessage('Keyboard:RemoveFocus', {}); }, set onfocuschange(val) { @@ -92,7 +92,7 @@ MozKeyboard.prototype = { return this._focusHandler; }, - handleMessage: function mozKeyboardHandleMessage(msg) { + receiveMessage: function mozKeyboardReceiveMessage(msg) { let handler = this._focusHandler; if (!handler || !(handler instanceof Ci.nsIDOMEventListener)) return; @@ -107,32 +107,9 @@ MozKeyboard.prototype = { }, observe: function mozKeyboardObserve(subject, topic, data) { - switch (topic) { - case "inner-window-destroyed": { - let wId = subject.QueryInterface(Ci.nsISupportsPRUint64).data; - if (wId == this.innerWindowID) { - this.uninit(); - } - break; - } - case 'remote-browser-frame-shown': - case 'in-process-browser-frame-shown': { - let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader); - let mm = frameLoader.messageManager; - mm.addMessageListener("Forms:Input", (function receiveMessage(msg) { - // Need to save mm here so later the message can be sent back to the - // correct app in the methods called by the value selector. - this._messageManager = mm; - this.handleMessage(msg); - }).bind(this)); - try { - mm.loadFrameScript(kFormsFrameScript, true); - } catch (e) { - dump('Error loading ' + kFormsFrameScript + ' as frame script: ' + e + '\n'); - } - break; - } - } + let wId = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + if (wId == this.innerWindowID) + this.uninit(); } }; From 18a52c313517b44b769f3ecd04eea8a0fde52afa Mon Sep 17 00:00:00 2001 From: Ed Morley Date: Wed, 7 Nov 2012 12:31:00 +0000 Subject: [PATCH 14/77] Backout 99a2125bd365 (bug 779971) for reftest failures --- layout/base/nsCSSFrameConstructor.cpp | 7 ----- layout/base/nsChangeHint.h | 8 +----- layout/svg/crashtests/779971-1.svg | 14 ---------- layout/svg/crashtests/crashtests.list | 1 - layout/svg/nsSVGEffects.cpp | 38 +++++++++++++++++++++++---- 5 files changed, 34 insertions(+), 34 deletions(-) delete mode 100644 layout/svg/crashtests/779971-1.svg diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 0cf2d45ae6c..2c191a0383f 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -116,7 +116,6 @@ #include "nsIDOMSVGFilters.h" #include "DOMSVGTests.h" #include "nsSVGEffects.h" -#include "nsSVGTextPathFrame.h" #include "nsSVGUtils.h" #include "nsRefreshDriver.h" @@ -7788,12 +7787,6 @@ DoApplyRenderingChangeToTree(nsIFrame* aFrame, needInvalidatingPaint = true; // Invalidate and update our area: nsSVGUtils::InvalidateAndScheduleReflowSVG(aFrame); - } else if (aChange & nsChangeHint_UpdateTextPath) { - NS_ABORT_IF_FALSE(aFrame->GetType() == nsGkAtoms::svgTextPathFrame, - "textPath frame expected"); - needInvalidatingPaint = true; - // Invalidate and update our area: - static_cast(aFrame)->NotifyGlyphMetricsChange(); } else { needInvalidatingPaint = true; // Just invalidate our area: diff --git a/layout/base/nsChangeHint.h b/layout/base/nsChangeHint.h index 977eae84acf..cb055fb1f97 100644 --- a/layout/base/nsChangeHint.h +++ b/layout/base/nsChangeHint.h @@ -111,13 +111,7 @@ enum nsChangeHint { * changes, and it's inherited by a child, that might require a reflow * due to the border-width change on the child. */ - nsChangeHint_BorderStyleNoneChange = 0x8000, - - /** - * SVG textPath needs to be recomputed because the path has changed. - * This means that the glyph positions of the text need to be recomputed. - */ - nsChangeHint_UpdateTextPath = 0x10000 + nsChangeHint_BorderStyleNoneChange = 0x8000 // IMPORTANT NOTE: When adding new hints, consider whether you need to // add them to NS_HintsNotHandledForDescendantsIn() below. diff --git a/layout/svg/crashtests/779971-1.svg b/layout/svg/crashtests/779971-1.svg deleted file mode 100644 index d57065a0ba8..00000000000 --- a/layout/svg/crashtests/779971-1.svg +++ /dev/null @@ -1,14 +0,0 @@ - -x1 - - diff --git a/layout/svg/crashtests/crashtests.list b/layout/svg/crashtests/crashtests.list index e026a384623..d01bb92fee5 100644 --- a/layout/svg/crashtests/crashtests.list +++ b/layout/svg/crashtests/crashtests.list @@ -136,7 +136,6 @@ load 767056-1.svg load 768351.svg load 780963-1.html load 757751-1.svg -load 779971-1.svg load 782141-1.svg load 784061-1.svg load 790072.svg diff --git a/layout/svg/nsSVGEffects.cpp b/layout/svg/nsSVGEffects.cpp index 03ae1891811..e307665c643 100644 --- a/layout/svg/nsSVGEffects.cpp +++ b/layout/svg/nsSVGEffects.cpp @@ -286,6 +286,32 @@ nsSVGMarkerProperty::DoUpdate() mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); } +class nsAsyncNotifyGlyphMetricsChange MOZ_FINAL : public nsIReflowCallback +{ +public: + nsAsyncNotifyGlyphMetricsChange(nsIFrame* aFrame) : mWeakFrame(aFrame) + { + } + + virtual bool ReflowFinished() + { + nsSVGTextPathFrame* frame = + static_cast(mWeakFrame.GetFrame()); + if (frame) { + frame->NotifyGlyphMetricsChange(); + } + delete this; + return true; + } + + virtual void ReflowCallbackCanceled() + { + delete this; + } + + nsWeakFrame mWeakFrame; +}; + void nsSVGTextPathProperty::DoUpdate() { @@ -296,11 +322,13 @@ nsSVGTextPathProperty::DoUpdate() NS_ASSERTION(mFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); if (mFrame->GetType() == nsGkAtoms::svgTextPathFrame) { - // Repaint asynchronously in case the path frame is being torn down - nsChangeHint changeHint = - nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_UpdateTextPath); - mFramePresShell->FrameConstructor()->PostRestyleEvent( - mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); + if (mFrame->PresContext()->PresShell()->IsReflowLocked()) { + nsIReflowCallback* cb = new nsAsyncNotifyGlyphMetricsChange(mFrame); + mFrame->PresContext()->PresShell()->PostReflowCallback(cb); + } else { + nsSVGTextPathFrame* textPathFrame = static_cast(mFrame); + textPathFrame->NotifyGlyphMetricsChange(); + } } } From 4cf4eb6947342e31046d0a04c4191a04e70f8f18 Mon Sep 17 00:00:00 2001 From: Michael Kaply Date: Wed, 7 Nov 2012 08:27:24 -0600 Subject: [PATCH 15/77] Bug 800498 - Check for null context in MainWindowHook. r=jmathies --- widget/windows/TaskbarPreview.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/widget/windows/TaskbarPreview.cpp b/widget/windows/TaskbarPreview.cpp index 3677dcc12ad..70fe7b317cf 100644 --- a/widget/windows/TaskbarPreview.cpp +++ b/widget/windows/TaskbarPreview.cpp @@ -408,6 +408,9 @@ TaskbarPreview::MainWindowHook(void *aContext, NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() || nMsg == WM_DESTROY, "Window hook proc called with wrong message"); + NS_ASSERTION(aContext, "Null context in MainWindowHook"); + if (!aContext) + return false; TaskbarPreview *preview = reinterpret_cast(aContext); if (nMsg == WM_DESTROY) { // nsWindow is being destroyed From cd9ac34e6f9d1b9743c43fd84d05aeb4f872f1da Mon Sep 17 00:00:00 2001 From: Anant Narayanan Date: Wed, 7 Nov 2012 06:51:38 -0800 Subject: [PATCH 16/77] Bug 803782: Reset found flag before enumerating audio devices; r=jesup --- dom/media/MediaManager.cpp | 2 + dom/media/tests/crashtests/803782.html | 50 ++++++++++++++++++++++ dom/media/tests/crashtests/crashtests.list | 1 + 3 files changed, 53 insertions(+) create mode 100644 dom/media/tests/crashtests/803782.html diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index e4bb30dc8d5..fcd8cf47376 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -504,6 +504,8 @@ public: } LOG(("Selected video device")); } + + found = false; if (mAudio) { nsTArray > audioSources; mBackend->EnumerateAudioDevices(&audioSources); diff --git a/dom/media/tests/crashtests/803782.html b/dom/media/tests/crashtests/803782.html new file mode 100644 index 00000000000..e219be4ff20 --- /dev/null +++ b/dom/media/tests/crashtests/803782.html @@ -0,0 +1,50 @@ + + + + + + + \ No newline at end of file diff --git a/dom/media/tests/crashtests/crashtests.list b/dom/media/tests/crashtests/crashtests.list index 9145a5915ea..58836573a43 100644 --- a/dom/media/tests/crashtests/crashtests.list +++ b/dom/media/tests/crashtests/crashtests.list @@ -2,3 +2,4 @@ pref(media.peerconnection.enabled,true) load 780790.html pref(media.peerconnection.enabled,true) load 791270.html pref(media.peerconnection.enabled,true) load 791278.html pref(media.peerconnection.enabled,true) load 791330.html +pref(media.peerconnection.enabled,true) load 803782.html \ No newline at end of file From 550ae65e3bfad97014073503105f25d8ee32f162 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 10:00:48 -0500 Subject: [PATCH 17/77] Bug 808003 - Refactor the code to add telemetry probes in browser.js. r=gcp --- mobile/android/chrome/content/browser.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index ec0ac4bf891..57d64469310 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -4490,8 +4490,6 @@ var ErrorPageEventHandler = { errorDoc.location = "about:home"; } } else if (/^about:blocked/.test(errorDoc.documentURI)) { - let secHistogram = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI"); - // The event came from a button on a malware/phishing block page // First check whether it's malware or phishing, so that we can // use the right strings/links @@ -4502,12 +4500,12 @@ var ErrorPageEventHandler = { let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); if (target == errorDoc.getElementById("getMeOutButton")) { - secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]); + Telemetry.addData("SECURITY_UI", nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]); errorDoc.location = "about:home"; } else if (target == errorDoc.getElementById("reportButton")) { // We log even if malware/phishing info URL couldn't be found: // the measurement is for how many users clicked the WHY BLOCKED button - secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]); + Telemetry.addData("SECURITY_UI", nsISecTel[bucketName + "WHY_BLOCKED"]); // This is the "Why is this site blocked" button. For malware, // we can fetch a site-specific report, for phishing, we redirect @@ -4531,7 +4529,7 @@ var ErrorPageEventHandler = { } } } else if (target == errorDoc.getElementById("ignoreWarningButton")) { - secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]); + Telemetry.addData("SECURITY_UI", nsISecTel[bucketName + "IGNORE_WARNING"]); // Allow users to override and continue through to the site, let webNav = BrowserApp.selectedBrowser.docShell.QueryInterface(Ci.nsIWebNavigation); @@ -7045,6 +7043,12 @@ var Telemetry = { Services.obs.removeObserver(this, "Telemetry:Prompt"); }, + addData: function addData(aHistogramId, aValue) { + let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + let histogram = telemetry.getHistogramById(aHistogramId); + histogram.add(aValue); + }, + observe: function observe(aSubject, aTopic, aData) { if (aTopic == "Preferences:Set") { // if user changes telemetry pref, treat it like they have been prompted @@ -7053,9 +7057,7 @@ var Telemetry = { Services.prefs.setIntPref(this._PREF_TELEMETRY_PROMPTED, this._TELEMETRY_PROMPT_REV); } else if (aTopic == "Telemetry:Add") { let json = JSON.parse(aData); - var telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); - let histogram = telemetry.getHistogramById(json.name); - histogram.add(json.value); + this.addData(json.name, json.value); } else if (aTopic == "Telemetry:Prompt") { #ifdef MOZ_TELEMETRY_REPORTING this.prompt(); From d974f62ce4704a5b973851591f998c5545fdda6c Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 10:00:48 -0500 Subject: [PATCH 18/77] Bug 808003 - Add a few telemetry probes to help tune tab zombification parameters. r=gcp, mfinkle --- mobile/android/chrome/content/browser.js | 7 ++++-- toolkit/components/telemetry/Histograms.json | 23 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 57d64469310..f56d83e4a78 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -7609,12 +7609,13 @@ var MemoryObserver = { for (let i = 0; i < tabs.length; i++) { if (tabs[i] != selected) { this.zombify(tabs[i]); + Telemetry.addData("FENNEC_TAB_ZOMBIFIED", (Date.now() - tabs[i].lastTouchedAt) / 1000); } } + Telemetry.addData("FENNEC_LOWMEM_TAB_COUNT", tabs.length); }, zombify: function(tab) { - dump("Zombifying tab at index [" + tab.id + "]"); let browser = tab.browser; let data = browser.__SS_data; let extra = browser.__SS_extdata; @@ -7771,8 +7772,10 @@ var Tabs = { } // if the tab was last touched more than browser.tabs.expireTime seconds ago, // zombify it - if (lruTab && (Date.now() - lruTab.lastTouchedAt) > expireTimeMs) { + let tabAgeMs = Date.now() - lruTab.lastTouchedAt; + if (lruTab && tabAgeMs > expireTimeMs) { MemoryObserver.zombify(lruTab); + Telemetry.addData("FENNEC_TAB_EXPIRED", tabAgeMs / 1000); return true; } return false; diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index e4c2d4b84e2..c73da70ce4b 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -2309,6 +2309,13 @@ "description": "Fennec: Time for the Awesomebar Top Sites query to return with no filter set (ms)", "cpp_guard": "ANDROID" }, + "FENNEC_LOWMEM_TAB_COUNT": { + "kind": "exponential", + "high": 100, + "n_buckets": 30, + "description": "How many tabs were open when a low-memory event was received", + "cpp_guard": "ANDROID" + }, "FENNEC_RESTORING_ACTIVITY": { "kind": "flag", "description": "Fennec is starting up but the Gecko thread was still running", @@ -2344,6 +2351,22 @@ "description": "The way the GeckoApp was launched. (Normal, URL, Prefetch, Redirector)", "cpp_guard": "ANDROID" }, + "FENNEC_TAB_EXPIRED": { + "kind": "exponential", + "low": 10, + "high": 604800, + "n_buckets": 20, + "description": "How long (in seconds) a tab was inactive before it was expired", + "cpp_guard": "ANDROID" + }, + "FENNEC_TAB_ZOMBIFIED": { + "kind": "exponential", + "low": 10, + "high": 1000000, + "n_buckets": 20, + "description": "How long (in seconds) a tab was inactive when it was OOM-zombified", + "cpp_guard": "ANDROID" + }, "FENNEC_WAS_KILLED": { "kind": "flag", "description": "Killed, likely due to an OOM condition", From 1a92bc6ab187a71f65716cce565ff2cd8e51587f Mon Sep 17 00:00:00 2001 From: Jonathan Watt Date: Wed, 7 Nov 2012 13:54:55 +0000 Subject: [PATCH 19/77] Bug 809328 - Stop using nsChangeHint_UpdateEffects in nsSVGRenderingObserver::DoUpdate overrides. r=roc. --HG-- extra : rebase_source : 79d032ea6ad6275882ab675004d2e02b932d56d5 --- layout/base/nsChangeHint.h | 13 ++++++------- layout/svg/nsSVGEffects.cpp | 4 ++-- layout/svg/nsSVGEffects.h | 15 +++++++++++++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/layout/base/nsChangeHint.h b/layout/base/nsChangeHint.h index cb055fb1f97..ff3b0253acd 100644 --- a/layout/base/nsChangeHint.h +++ b/layout/base/nsChangeHint.h @@ -43,13 +43,12 @@ enum nsChangeHint { nsChangeHint_UpdateCursor = 0x40, /** - * SVG filter/mask/clip effects need to be recomputed because the URI - * in the filter/mask/clip-path property has changed. This wipes - * out cached nsSVGPropertyBase and subclasses which hold a reference to - * the element referenced by the URI, and a mutation observer for - * the DOM subtree rooted at that element. Also, for filters they store a - * bounding-box for the filter result so that if the filter changes we can - * invalidate the old covered area. + * Used when the computed value (a URI) of one or more of an element's + * filter/mask/clip/etc CSS properties changes, causing the element's frame + * to start/stop referencing (or reference different) SVG resource elements. + * (_Not_ used to handle changes to referenced resource elements.) Using this + * hint results in nsSVGEffects::UpdateEffects being called on the element's + * frame. */ nsChangeHint_UpdateEffects = 0x80, diff --git a/layout/svg/nsSVGEffects.cpp b/layout/svg/nsSVGEffects.cpp index e307665c643..f6830b264d6 100644 --- a/layout/svg/nsSVGEffects.cpp +++ b/layout/svg/nsSVGEffects.cpp @@ -253,7 +253,7 @@ nsSVGFilterProperty::DoUpdate() // Repaint asynchronously in case the filter frame is being torn down nsChangeHint changeHint = - nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_UpdateEffects); + nsChangeHint(nsChangeHint_RepaintFrame); // Don't need to request UpdateOverflow if we're being reflowed. if (!(mFrame->GetStateBits() & NS_FRAME_IN_REFLOW)) { @@ -274,7 +274,7 @@ nsSVGMarkerProperty::DoUpdate() // Repaint asynchronously in case the marker frame is being torn down nsChangeHint changeHint = - nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_UpdateEffects); + nsChangeHint(nsChangeHint_RepaintFrame); // Don't need to request ReflowFrame if we're being reflowed. if (!(mFrame->GetStateBits() & NS_FRAME_IN_REFLOW)) { diff --git a/layout/svg/nsSVGEffects.h b/layout/svg/nsSVGEffects.h index 544afd94dd9..c8e9281fa67 100644 --- a/layout/svg/nsSVGEffects.h +++ b/layout/svg/nsSVGEffects.h @@ -338,11 +338,22 @@ public: * @param aFrame should be the first continuation */ static EffectProperties GetEffectProperties(nsIFrame *aFrame); + /** - * Called by nsCSSFrameConstructor when style changes require the - * effect properties on aFrame to be updated + * Called when changes to an element (e.g. CSS property changes) cause its + * frame to start/stop referencing (or reference different) SVG resource + * elements. (_Not_ called for changes to referenced resource elements.) + * + * This function handles such changes by discarding _all_ the frame's SVG + * effects frame properties (causing those properties to stop watching their + * target element). It also synchronously (re)creates the filter and marker + * frame properties (XXX why not the other properties?), which makes it + * useful for initializing those properties during first reflow. + * + * XXX rename to something more meaningful like RefreshResourceReferences? */ static void UpdateEffects(nsIFrame *aFrame); + /** * @param aFrame should be the first continuation */ From 7d1926573607572761bcdba711c5d8760fdf6648 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 10:09:35 -0500 Subject: [PATCH 20/77] Bug 808003 - Follow-up for 4f5f601bc741; update the range on the FENNEC_TAB_ZOMBIFIED histogram as well. r=gcp --- toolkit/components/telemetry/Histograms.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index c73da70ce4b..1be7aece693 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -2362,7 +2362,7 @@ "FENNEC_TAB_ZOMBIFIED": { "kind": "exponential", "low": 10, - "high": 1000000, + "high": 604800, "n_buckets": 20, "description": "How long (in seconds) a tab was inactive when it was OOM-zombified", "cpp_guard": "ANDROID" From 3bab4a3b7bd2e40d382fa94852b11756e24bbe2a Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Wed, 7 Nov 2012 15:45:35 +0000 Subject: [PATCH 21/77] Bug 803571 - Clear database before refreshing about:home (r=mfinkle) --- mobile/android/base/BrowserApp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index 5725adf2f57..70efaeb8ae1 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -155,8 +155,8 @@ abstract public class BrowserApp extends GeckoApp @Override void handleClearHistory() { - updateAboutHomeTopSites(); super.handleClearHistory(); + updateAboutHomeTopSites(); } @Override From 6d4035604b9903ebd6f8a390e6937980eccf5bdf Mon Sep 17 00:00:00 2001 From: Anant Narayanan Date: Wed, 7 Nov 2012 08:10:36 -0800 Subject: [PATCH 22/77] Revert d2f77172baf2 due to incorrect crashtest --- dom/media/MediaManager.cpp | 2 - dom/media/tests/crashtests/803782.html | 50 ---------------------- dom/media/tests/crashtests/crashtests.list | 1 - 3 files changed, 53 deletions(-) delete mode 100644 dom/media/tests/crashtests/803782.html diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index fcd8cf47376..e4bb30dc8d5 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -504,8 +504,6 @@ public: } LOG(("Selected video device")); } - - found = false; if (mAudio) { nsTArray > audioSources; mBackend->EnumerateAudioDevices(&audioSources); diff --git a/dom/media/tests/crashtests/803782.html b/dom/media/tests/crashtests/803782.html deleted file mode 100644 index e219be4ff20..00000000000 --- a/dom/media/tests/crashtests/803782.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/dom/media/tests/crashtests/crashtests.list b/dom/media/tests/crashtests/crashtests.list index 58836573a43..9145a5915ea 100644 --- a/dom/media/tests/crashtests/crashtests.list +++ b/dom/media/tests/crashtests/crashtests.list @@ -2,4 +2,3 @@ pref(media.peerconnection.enabled,true) load 780790.html pref(media.peerconnection.enabled,true) load 791270.html pref(media.peerconnection.enabled,true) load 791278.html pref(media.peerconnection.enabled,true) load 791330.html -pref(media.peerconnection.enabled,true) load 803782.html \ No newline at end of file From e207f505f8696e29b8896574c7e4b6764cd1e709 Mon Sep 17 00:00:00 2001 From: Anant Narayanan Date: Wed, 7 Nov 2012 08:12:14 -0800 Subject: [PATCH 23/77] Bug 803782: Reset found flag while enumerating audio devices; r=jesup --- dom/media/MediaManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index e4bb30dc8d5..fcd8cf47376 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -504,6 +504,8 @@ public: } LOG(("Selected video device")); } + + found = false; if (mAudio) { nsTArray > audioSources; mBackend->EnumerateAudioDevices(&audioSources); From 0d4bc850b418616e0aa81594c2e529c87e7d34ce Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Wed, 7 Nov 2012 08:27:23 -0800 Subject: [PATCH 24/77] Bug 808611 - Move valueOf to nsIDOMLocation. r=mrbkap --- dom/base/nsLocation.cpp | 8 ++++++++ dom/interfaces/base/nsIDOMLocation.idl | 3 ++- js/xpconnect/src/XPCJSRuntime.cpp | 1 - js/xpconnect/src/xpcprivate.h | 1 - js/xpconnect/wrappers/XrayWrapper.cpp | 24 ------------------------ 5 files changed, 10 insertions(+), 27 deletions(-) diff --git a/dom/base/nsLocation.cpp b/dom/base/nsLocation.cpp index 1bc67bbb25f..23d19b3706e 100644 --- a/dom/base/nsLocation.cpp +++ b/dom/base/nsLocation.cpp @@ -913,6 +913,14 @@ nsLocation::ToString(nsAString& aReturn) return GetHref(aReturn); } +NS_IMETHODIMP +nsLocation::ValueOf(nsIDOMLocation** aReturn) +{ + nsCOMPtr loc(this); + loc.forget(aReturn); + return NS_OK; +} + nsresult nsLocation::GetSourceBaseURL(JSContext* cx, nsIURI** sourceURL) { diff --git a/dom/interfaces/base/nsIDOMLocation.idl b/dom/interfaces/base/nsIDOMLocation.idl index 17c46265ef5..a0ecf16f452 100644 --- a/dom/interfaces/base/nsIDOMLocation.idl +++ b/dom/interfaces/base/nsIDOMLocation.idl @@ -5,7 +5,7 @@ #include "domstubs.idl" -[scriptable, uuid(a6cf906d-15b3-11d2-932e-00805f8add32)] +[scriptable, uuid(9472bf0f-2d1c-415c-90fd-f4260678b73b)] interface nsIDOMLocation : nsISupports { /** @@ -27,4 +27,5 @@ interface nsIDOMLocation : nsISupports void assign(in DOMString url); DOMString toString(); + nsIDOMLocation valueOf(); }; diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 840cf8d59bd..bdaf4bae0ed 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -48,7 +48,6 @@ const char* XPCJSRuntime::mStrings[] = { "constructor", // IDX_CONSTRUCTOR "toString", // IDX_TO_STRING "toSource", // IDX_TO_SOURCE - "valueOf", // IDX_VALUE_OF "lastResult", // IDX_LAST_RESULT "returnCode", // IDX_RETURN_CODE "value", // IDX_VALUE diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index f60d86d3eac..1a7b3c6e917 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -768,7 +768,6 @@ public: IDX_CONSTRUCTOR = 0 , IDX_TO_STRING , IDX_TO_SOURCE , - IDX_VALUE_OF , IDX_LAST_RESULT , IDX_RETURN_CODE , IDX_VALUE , diff --git a/js/xpconnect/wrappers/XrayWrapper.cpp b/js/xpconnect/wrappers/XrayWrapper.cpp index ad5debb062c..55604686908 100644 --- a/js/xpconnect/wrappers/XrayWrapper.cpp +++ b/js/xpconnect/wrappers/XrayWrapper.cpp @@ -731,13 +731,6 @@ XPCWrappedNativeXrayTraits::preserveWrapper(JSObject *target) ci->PreserveWrapper(wn->Native()); } -static JSBool -IdentityValueOf(JSContext *cx, unsigned argc, jsval *vp) -{ - JS_SET_RVAL(cx, vp, JS_THIS(cx, vp)); - return true; -} - bool XPCWrappedNativeXrayTraits::resolveNativeProperty(JSContext *cx, JSObject *wrapper, JSObject *holder, jsid id, bool set, @@ -768,23 +761,6 @@ XPCWrappedNativeXrayTraits::resolveNativeProperty(JSContext *cx, JSObject *wrapp return true; } - // Explicitly make valueOf an identity operation so that it plays better - // with the rest of the Xray infrastructure. - if (id == rt->GetStringID(XPCJSRuntime::IDX_VALUE_OF) && - Is(wrapper)) - { - JSFunction *fun = JS_NewFunctionById(cx, &IdentityValueOf, 0, 0, NULL, id); - if (!fun) - return false; - desc->obj = wrapper; - desc->attrs = 0; - desc->getter = NULL; - desc->setter = NULL; - desc->shortid = 0; - desc->value = ObjectValue(*JS_GetFunctionObject(fun)); - return true; - } - desc->obj = NULL; // This will do verification and the method lookup for us. From 42c232e7e3b9eaaf7a57cc13b428fec368e0e1e7 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Wed, 7 Nov 2012 08:28:35 -0800 Subject: [PATCH 25/77] Bug 808457 - Add Telemetry for Components.lookupMethod and Components.interfaces. r=mrbkap --- js/xpconnect/src/XPCComponents.cpp | 12 ++++++++++++ toolkit/components/telemetry/Histograms.json | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 4e3907af72a..f3f9477bf10 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -4864,6 +4864,12 @@ nsXPCComponents::CanCallMethod(const nsIID * iid, const PRUnichar *methodName, c { static const char* allowed[] = { "isSuccessCode", "lookupMethod", nullptr }; *_retval = xpc_CheckAccessList(methodName, allowed); + if (*_retval && + methodName[0] == 'l' && + !AccessCheck::callerIsXBL(nsContentUtils::GetCurrentJSContext())) + { + Telemetry::Accumulate(Telemetry::COMPONENTS_LOOKUPMETHOD_ACCESSED_BY_CONTENT, true); + } return NS_OK; } @@ -4873,6 +4879,12 @@ nsXPCComponents::CanGetProperty(const nsIID * iid, const PRUnichar *propertyName { static const char* allowed[] = { "interfaces", "interfacesByID", "results", nullptr}; *_retval = xpc_CheckAccessList(propertyName, allowed); + if (*_retval && + propertyName[0] == 'i' && + !AccessCheck::callerIsXBL(nsContentUtils::GetCurrentJSContext())) + { + Telemetry::Accumulate(Telemetry::COMPONENTS_INTERFACES_ACCESSED_BY_CONTENT, true); + } return NS_OK; } diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 1be7aece693..9a3a12df0d9 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -2401,5 +2401,13 @@ "COMPONENTS_OBJECT_ACCESSED_BY_CONTENT": { "kind": "flag", "description": "Whether content ever accesed the Components object in this session" + }, + "COMPONENTS_LOOKUPMETHOD_ACCESSED_BY_CONTENT": { + "kind": "flag", + "description": "Whether content ever accesed Components.lookupMethod in this session" + }, + "COMPONENTS_INTERFACES_ACCESSED_BY_CONTENT": { + "kind": "flag", + "description": "Whether content ever accesed Components.interfaces in this session" } } From baf27e7b331d8555cafe38f7814e06391dba31d9 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 7 Nov 2012 17:32:09 +0100 Subject: [PATCH 26/77] Bug 802073 - Follow-up: check IME mode for text inputs only r=yxl a=blocking-basecamp --- b2g/chrome/content/forms.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/b2g/chrome/content/forms.js b/b2g/chrome/content/forms.js index 6f8e090f2d4..0d4dea9fadd 100644 --- a/b2g/chrome/content/forms.js +++ b/b2g/chrome/content/forms.js @@ -90,7 +90,7 @@ let FormAssistant = { switch (evt.type) { case "focus": - if (this.isIMEDisabled()) + if (this.isTextInputElement(target) && this.isIMEDisabled()) return; if (target && this.isFocusableElement(target)) { @@ -195,7 +195,7 @@ let FormAssistant = { case "ime-enabled-state-changed": let shouldOpen = parseInt(data); let target = Services.fm.focusedElement; - if (!target) + if (!target || !this.isTextInputElement(target)) return; if (shouldOpen) { @@ -232,8 +232,7 @@ let FormAssistant = { target = target.parentNode; let kbOpened = this.tryShowIme(target); - if (target instanceof HTMLInputElement || - target instanceof HTMLTextAreaElement) + if (this.isTextInputElement(target)) this.isKeyboardOpened = kbOpened; this.setFocusedElement(target); @@ -258,6 +257,11 @@ let FormAssistant = { !this.ignoredInputTypes.has(element.type)); }, + isTextInputElement: function fa_isTextInputElement(element) { + return element instanceof HTMLInputElement || + element instanceof HTMLTextAreaElement; + }, + tryShowIme: function(element) { // FIXME/bug 729623: work around apparent bug in the IME manager // in gecko. From 4f53eee1c004b0d099c0987cfac7e0f7fb708993 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Thu, 1 Nov 2012 12:27:37 -0400 Subject: [PATCH 27/77] Bug 803665 - part 0 - make nsTimeout properly initialize its fields; r=bz --- dom/base/nsGlobalWindow.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 732ef0ec67e..c130b6a9936 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -533,6 +533,14 @@ private: NS_IMPL_ISUPPORTS2(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor) nsTimeout::nsTimeout() + : mCleared(false), + mRunning(false), + mIsInterval(false), + mPublicId(0), + mInterval(0), + mFiringDepth(0), + mNestingLevel(0), + mPopupState(openAllowed) { #ifdef DEBUG_jst { @@ -542,8 +550,6 @@ nsTimeout::nsTimeout() } #endif - memset(this, 0, sizeof(*this)); - MOZ_COUNT_CTOR(nsTimeout); } From fdfe7bdd60ef9eae878f632d8fcb48ebff961199 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Thu, 1 Nov 2012 10:06:22 -0400 Subject: [PATCH 28/77] Bug 803665 - convert nsGlobalWindow's timeout list to use mozilla::LinkedList; r=bz --- dom/base/nsGlobalWindow.cpp | 48 +++++++++++++++++-------------------- dom/base/nsGlobalWindow.h | 21 ++++++---------- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index c130b6a9936..d469b43253a 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -729,9 +729,6 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow) // Initialize the PRCList (this). PR_INIT_CLIST(this); - // Initialize timeout storage - PR_INIT_CLIST(&mTimeouts); - if (aOuterWindow) { // |this| is an inner window, add this inner window to the outer // window list of inners. @@ -1321,7 +1318,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow) nsEventListenerManager) for (nsTimeout* timeout = tmp->FirstTimeout(); - tmp->IsTimeout(timeout); + timeout; timeout = timeout->Next()) { cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(nsTimeout)); } @@ -1427,9 +1424,7 @@ nsGlobalWindow::IsBlackForCC() void nsGlobalWindow::UnmarkGrayTimers() { - for (nsTimeout* timeout = FirstTimeout(); - timeout && IsTimeout(timeout); - timeout = timeout->Next()) { + for (nsTimeout* timeout = FirstTimeout(); timeout; timeout = timeout->Next()) { if (timeout->mScriptHandler) { JSObject* o = timeout->mScriptHandler->GetScriptObject(); xpc_UnmarkGrayObject(o); @@ -2319,8 +2314,7 @@ nsGlobalWindow::DetachFromDocShell() // (mJSObject) so that it can be retrieved later (until it is // finalized by the JS GC). - NS_ASSERTION(PR_CLIST_IS_EMPTY(&mTimeouts), - "Uh, outer window holds timeouts!"); + NS_ASSERTION(mTimeouts.isEmpty(), "Uh, outer window holds timeouts!"); // Call FreeInnerObjects on all inner windows, not just the current // one, since some could be held by WindowStateHolder objects that @@ -9905,7 +9899,7 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout) // timeout events fire "early", so we need to test the timer as well // as the deadline. last_expired_timeout = nullptr; - for (timeout = FirstTimeout(); IsTimeout(timeout); timeout = timeout->Next()) { + for (timeout = FirstTimeout(); timeout; timeout = timeout->Next()) { if (((timeout == aTimeout) || (timeout->mWhen <= deadline)) && (timeout->mFiringDepth == 0)) { // Mark any timeouts that are on the list to be fired with the @@ -9939,7 +9933,7 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout) // list for any timeouts inserted as a result of running a timeout. dummy_timeout.mFiringDepth = firingDepth; dummy_timeout.mWhen = now; - PR_INSERT_AFTER(&dummy_timeout, last_expired_timeout); + last_expired_timeout->setNext(&dummy_timeout); // Don't let ClearWindowTimeouts throw away our stack-allocated // dummy timeout. @@ -10022,7 +10016,7 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout) // we need to reset the pointer to the following timeout. nextTimeout = timeout->Next(); - PR_REMOVE_LINK(timeout); + timeout->remove(); if (needsReinsertion) { // Insert interval timeout onto list sorted in deadline order. @@ -10035,7 +10029,7 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout) } // Take the dummy timeout off the head of the list - PR_REMOVE_LINK(&dummy_timeout); + dummy_timeout.remove(); mTimeoutInsertionPoint = last_insertion_point; } @@ -10073,9 +10067,7 @@ nsGlobalWindow::ClearTimeoutOrInterval(int32_t aTimerID) uint32_t public_id = (uint32_t)aTimerID; nsTimeout *timeout; - for (timeout = FirstTimeout(); - IsTimeout(timeout); - timeout = timeout->Next()) { + for (timeout = FirstTimeout(); timeout; timeout = timeout->Next()) { if (timeout->mPublicId == public_id) { if (timeout->mRunning) { /* We're running from inside the timeout. Mark this @@ -10085,7 +10077,7 @@ nsGlobalWindow::ClearTimeoutOrInterval(int32_t aTimerID) } else { /* Delete the timeout from the pending timeout list */ - PR_REMOVE_LINK(timeout); + timeout->remove(); if (timeout->mTimer) { timeout->mTimer->Cancel(); @@ -10121,7 +10113,7 @@ nsresult nsGlobalWindow::ResetTimersForNonBackgroundWindow() // Otherwise, start at the beginning of the list. for (nsTimeout *timeout = mTimeoutInsertionPoint ? mTimeoutInsertionPoint->Next() : FirstTimeout(); - IsTimeout(timeout); ) { + timeout; ) { // It's important that this check be <= so that we guarantee that // taking NS_MAX with |now| won't make a quantity equal to // timeout->mWhen below. @@ -10168,9 +10160,9 @@ nsresult nsGlobalWindow::ResetTimersForNonBackgroundWindow() // It is safe to remove and re-insert because mWhen is now // strictly smaller than it used to be, so we know we'll insert // |timeout| before nextTimeout. - NS_ASSERTION(!IsTimeout(nextTimeout) || + NS_ASSERTION(!nextTimeout || timeout->mWhen < nextTimeout->mWhen, "How did that happen?"); - PR_REMOVE_LINK(timeout); + timeout->remove(); // InsertTimeoutIntoList will addref |timeout| and reset // mFiringDepth. Make sure to undo that after calling it. uint32_t firingDepth = timeout->mFiringDepth; @@ -10199,7 +10191,7 @@ nsGlobalWindow::ClearAllTimeouts() { nsTimeout *timeout, *nextTimeout; - for (timeout = FirstTimeout(); IsTimeout(timeout); timeout = nextTimeout) { + for (timeout = FirstTimeout(); timeout; timeout = nextTimeout) { /* If RunTimeout() is higher up on the stack for this window, e.g. as a result of document.write from a timeout, then we need to reset the list insertion point for @@ -10228,7 +10220,7 @@ nsGlobalWindow::ClearAllTimeouts() } // Clear out our list - PR_INIT_CLIST(&mTimeouts); + mTimeouts.clear(); } void @@ -10242,7 +10234,7 @@ nsGlobalWindow::InsertTimeoutIntoList(nsTimeout *aTimeout) // insertion at the end. nsTimeout* prevSibling; for (prevSibling = LastTimeout(); - IsTimeout(prevSibling) && prevSibling != mTimeoutInsertionPoint && + prevSibling && prevSibling != mTimeoutInsertionPoint && // This condition needs to match the one in SetTimeoutOrInterval that // determines whether to set mWhen or mTimeRemaining. ((IsFrozen() || mTimeoutsSuspendDepth) ? @@ -10253,7 +10245,11 @@ nsGlobalWindow::InsertTimeoutIntoList(nsTimeout *aTimeout) } // Now link in aTimeout after prevSibling. - PR_INSERT_AFTER(aTimeout, prevSibling); + if (prevSibling) { + prevSibling->setNext(aTimeout); + } else { + mTimeouts.insertFront(aTimeout); + } aTimeout->mFiringDepth = 0; @@ -10545,7 +10541,7 @@ nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease, mozilla::dom::workers::SuspendWorkersForWindow(cx, this); TimeStamp now = TimeStamp::Now(); - for (nsTimeout *t = FirstTimeout(); IsTimeout(t); t = t->Next()) { + for (nsTimeout *t = FirstTimeout(); t; t = t->Next()) { // Set mTimeRemaining to be the time remaining for this timer. if (t->mWhen > now) t->mTimeRemaining = t->mWhen - now; @@ -10633,7 +10629,7 @@ nsGlobalWindow::ResumeTimeouts(bool aThawChildren) bool _seenDummyTimeout = false; #endif - for (nsTimeout *t = FirstTimeout(); IsTimeout(t); t = t->Next()) { + for (nsTimeout *t = FirstTimeout(); t; t = t->Next()) { // There's a chance we're being called with RunTimeout on the stack in which // case we have a dummy timeout in the list that *must not* be resumed. It // can be identified by a null mWindow. diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index 9552417cd93..49caaf03a7f 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -59,6 +59,7 @@ #include "nsIContent.h" #include "nsIIDBFactory.h" #include "nsFrameMessageManager.h" +#include "mozilla/LinkedList.h" #include "mozilla/TimeStamp.h" #include "nsIDOMTouchEvent.h" #include "nsIInlineEventHandlers.h" @@ -134,7 +135,7 @@ NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow, * timeout. Holds a strong reference to an nsIScriptTimeoutHandler, which * abstracts the language specific cruft. */ -struct nsTimeout : PRCList +struct nsTimeout : mozilla::LinkedListElement { nsTimeout(); ~nsTimeout(); @@ -145,13 +146,11 @@ struct nsTimeout : PRCList nsrefcnt AddRef(); nsTimeout* Next() { - // Note: might not actually return an nsTimeout. Use IsTimeout to check. - return static_cast(PR_NEXT_LINK(this)); + return getNext(); } nsTimeout* Prev() { - // Note: might not actually return an nsTimeout. Use IsTimeout to check. - return static_cast(PR_PREV_LINK(this)); + return getPrevious(); } nsresult InitTimer(nsTimerCallbackFunc aFunc, uint64_t delay) { @@ -873,17 +872,11 @@ protected: bool IsInModalState(); nsTimeout* FirstTimeout() { - // Note: might not actually return an nsTimeout. Use IsTimeout to check. - return static_cast(PR_LIST_HEAD(&mTimeouts)); + return mTimeouts.getFirst(); } nsTimeout* LastTimeout() { - // Note: might not actually return an nsTimeout. Use IsTimeout to check. - return static_cast(PR_LIST_TAIL(&mTimeouts)); - } - - bool IsTimeout(PRCList* aList) { - return aList != &mTimeouts; + return mTimeouts.getLast(); } // Convenience functions for the many methods that need to scale @@ -1053,7 +1046,7 @@ protected: // non-null. In that case, the dummy timeout pointed to by // mTimeoutInsertionPoint may have a later mWhen than some of the timeouts // that come after it. - PRCList mTimeouts; + mozilla::LinkedList mTimeouts; // If mTimeoutInsertionPoint is non-null, insertions should happen after it. // This is a dummy timeout at the moment; if that ever changes, the logic in // ResetTimersForNonBackgroundWindow needs to change. From 3fe7c0dc6e7e96f4578d54103481474eda5f2a36 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Thu, 1 Nov 2012 10:07:22 -0400 Subject: [PATCH 29/77] Bug 803665 - followup - dispense with nsGlobalWindow::{First,Last}Timeout and nsTimeout::{Next,Previous}; r=bz --- dom/base/nsGlobalWindow.cpp | 38 +++++++++++++++++++------------------ dom/base/nsGlobalWindow.h | 16 ---------------- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index d469b43253a..2ee170e0c59 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1317,9 +1317,9 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mListenerManager, nsEventListenerManager) - for (nsTimeout* timeout = tmp->FirstTimeout(); + for (nsTimeout* timeout = tmp->mTimeouts.getFirst(); timeout; - timeout = timeout->Next()) { + timeout = timeout->getNext()) { cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(nsTimeout)); } @@ -1424,7 +1424,9 @@ nsGlobalWindow::IsBlackForCC() void nsGlobalWindow::UnmarkGrayTimers() { - for (nsTimeout* timeout = FirstTimeout(); timeout; timeout = timeout->Next()) { + for (nsTimeout* timeout = mTimeouts.getFirst(); + timeout; + timeout = timeout->getNext()) { if (timeout->mScriptHandler) { JSObject* o = timeout->mScriptHandler->GetScriptObject(); xpc_UnmarkGrayObject(o); @@ -9899,7 +9901,7 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout) // timeout events fire "early", so we need to test the timer as well // as the deadline. last_expired_timeout = nullptr; - for (timeout = FirstTimeout(); timeout; timeout = timeout->Next()) { + for (timeout = mTimeouts.getFirst(); timeout; timeout = timeout->getNext()) { if (((timeout == aTimeout) || (timeout->mWhen <= deadline)) && (timeout->mFiringDepth == 0)) { // Mark any timeouts that are on the list to be fired with the @@ -9947,10 +9949,10 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout) Telemetry::AutoCounter timeoutsRan; - for (timeout = FirstTimeout(); + for (timeout = mTimeouts.getFirst(); timeout != &dummy_timeout && !IsFrozen(); timeout = nextTimeout) { - nextTimeout = timeout->Next(); + nextTimeout = timeout->getNext(); if (timeout->mFiringDepth != firingDepth) { // We skip the timeout since it's on the list to run at another @@ -10014,7 +10016,7 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout) // Running a timeout can cause another timeout to be deleted, so // we need to reset the pointer to the following timeout. - nextTimeout = timeout->Next(); + nextTimeout = timeout->getNext(); timeout->remove(); @@ -10067,7 +10069,7 @@ nsGlobalWindow::ClearTimeoutOrInterval(int32_t aTimerID) uint32_t public_id = (uint32_t)aTimerID; nsTimeout *timeout; - for (timeout = FirstTimeout(); timeout; timeout = timeout->Next()) { + for (timeout = mTimeouts.getFirst(); timeout; timeout = timeout->getNext()) { if (timeout->mPublicId == public_id) { if (timeout->mRunning) { /* We're running from inside the timeout. Mark this @@ -10112,13 +10114,13 @@ nsresult nsGlobalWindow::ResetTimersForNonBackgroundWindow() // start at the timer after mTimeoutInsertionPoint, if there is one. // Otherwise, start at the beginning of the list. for (nsTimeout *timeout = mTimeoutInsertionPoint ? - mTimeoutInsertionPoint->Next() : FirstTimeout(); + mTimeoutInsertionPoint->getNext() : mTimeouts.getFirst(); timeout; ) { // It's important that this check be <= so that we guarantee that // taking NS_MAX with |now| won't make a quantity equal to // timeout->mWhen below. if (timeout->mWhen <= now) { - timeout = timeout->Next(); + timeout = timeout->getNext(); continue; } @@ -10155,7 +10157,7 @@ nsresult nsGlobalWindow::ResetTimersForNonBackgroundWindow() // Get the pointer to the next timeout now, before we move the // current timeout in the list. - nsTimeout* nextTimeout = timeout->Next(); + nsTimeout* nextTimeout = timeout->getNext(); // It is safe to remove and re-insert because mWhen is now // strictly smaller than it used to be, so we know we'll insert @@ -10179,7 +10181,7 @@ nsresult nsGlobalWindow::ResetTimersForNonBackgroundWindow() timeout = nextTimeout; } else { - timeout = timeout->Next(); + timeout = timeout->getNext(); } } @@ -10191,7 +10193,7 @@ nsGlobalWindow::ClearAllTimeouts() { nsTimeout *timeout, *nextTimeout; - for (timeout = FirstTimeout(); timeout; timeout = nextTimeout) { + for (timeout = mTimeouts.getFirst(); timeout; timeout = nextTimeout) { /* If RunTimeout() is higher up on the stack for this window, e.g. as a result of document.write from a timeout, then we need to reset the list insertion point for @@ -10200,7 +10202,7 @@ nsGlobalWindow::ClearAllTimeouts() if (mRunningTimeout == timeout) mTimeoutInsertionPoint = nullptr; - nextTimeout = timeout->Next(); + nextTimeout = timeout->getNext(); if (timeout->mTimer) { timeout->mTimer->Cancel(); @@ -10233,14 +10235,14 @@ nsGlobalWindow::InsertTimeoutIntoList(nsTimeout *aTimeout) // mTimeoutInsertionPoint, though. This optimizes for the common case of // insertion at the end. nsTimeout* prevSibling; - for (prevSibling = LastTimeout(); + for (prevSibling = mTimeouts.getLast(); prevSibling && prevSibling != mTimeoutInsertionPoint && // This condition needs to match the one in SetTimeoutOrInterval that // determines whether to set mWhen or mTimeRemaining. ((IsFrozen() || mTimeoutsSuspendDepth) ? prevSibling->mTimeRemaining > aTimeout->mTimeRemaining : prevSibling->mWhen > aTimeout->mWhen); - prevSibling = prevSibling->Prev()) { + prevSibling = prevSibling->getPrevious()) { /* Do nothing; just searching */ } @@ -10541,7 +10543,7 @@ nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease, mozilla::dom::workers::SuspendWorkersForWindow(cx, this); TimeStamp now = TimeStamp::Now(); - for (nsTimeout *t = FirstTimeout(); t; t = t->Next()) { + for (nsTimeout *t = mTimeouts.getFirst(); t; t = t->getNext()) { // Set mTimeRemaining to be the time remaining for this timer. if (t->mWhen > now) t->mTimeRemaining = t->mWhen - now; @@ -10629,7 +10631,7 @@ nsGlobalWindow::ResumeTimeouts(bool aThawChildren) bool _seenDummyTimeout = false; #endif - for (nsTimeout *t = FirstTimeout(); t; t = t->Next()) { + for (nsTimeout *t = mTimeouts.getFirst(); t; t = t->getNext()) { // There's a chance we're being called with RunTimeout on the stack in which // case we have a dummy timeout in the list that *must not* be resumed. It // can be identified by a null mWindow. diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index 49caaf03a7f..dbbcbafceb8 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -145,14 +145,6 @@ struct nsTimeout : mozilla::LinkedListElement nsrefcnt Release(); nsrefcnt AddRef(); - nsTimeout* Next() { - return getNext(); - } - - nsTimeout* Prev() { - return getPrevious(); - } - nsresult InitTimer(nsTimerCallbackFunc aFunc, uint64_t delay) { return mTimer->InitWithFuncCallback(aFunc, this, delay, nsITimer::TYPE_ONE_SHOT); @@ -871,14 +863,6 @@ protected: bool IsInModalState(); - nsTimeout* FirstTimeout() { - return mTimeouts.getFirst(); - } - - nsTimeout* LastTimeout() { - return mTimeouts.getLast(); - } - // Convenience functions for the many methods that need to scale // from device to CSS pixels or vice versa. Note: if a presentation // context is not available, they will assume a 1:1 ratio. From 4fec79346f16b79eecc2784fe61a216497ca700c Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Thu, 1 Nov 2012 10:03:41 -0400 Subject: [PATCH 30/77] Bug 803668 - convert string bundle caches to use mozilla::LinkedList; r=smontagu --- intl/strres/src/nsStringBundle.cpp | 25 ++++++++----------------- intl/strres/src/nsStringBundleService.h | 5 +++-- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/intl/strres/src/nsStringBundle.cpp b/intl/strres/src/nsStringBundle.cpp index ad83961dab8..53cb270fff1 100644 --- a/intl/strres/src/nsStringBundle.cpp +++ b/intl/strres/src/nsStringBundle.cpp @@ -501,8 +501,7 @@ nsresult nsExtensibleStringBundle::GetSimpleEnumeration(nsISimpleEnumerator ** a #define MAX_CACHED_BUNDLES 16 -struct bundleCacheEntry_t { - PRCList list; +struct bundleCacheEntry_t : public LinkedListElement { nsCStringKey *mHashKey; // do not use a nsCOMPtr - this is a struct not a class! nsIStringBundle* mBundle; @@ -516,7 +515,6 @@ nsStringBundleService::nsStringBundleService() : printf("\n++ nsStringBundleService::nsStringBundleService ++\n"); #endif - PR_INIT_CLIST(&mBundleCache); PL_InitArenaPool(&mCacheEntryPool, "srEntries", sizeof(bundleCacheEntry_t)*MAX_CACHED_BUNDLES, sizeof(bundleCacheEntry_t)); @@ -582,16 +580,10 @@ nsStringBundleService::flushBundleCache() // release all bundles in the cache mBundleMap.Reset(); - PRCList *current = PR_LIST_HEAD(&mBundleCache); - while (current != &mBundleCache) { - bundleCacheEntry_t *cacheEntry = (bundleCacheEntry_t*)current; + while (!mBundleCache.isEmpty()) { + bundleCacheEntry_t *cacheEntry = mBundleCache.popFirst(); recycleEntry(cacheEntry); - PRCList *oldItem = current; - current = PR_NEXT_LINK(current); - - // will be freed in PL_FreeArenaPool - PR_REMOVE_LINK(oldItem); } PL_FreeArenaPool(&mCacheEntryPool); } @@ -616,7 +608,7 @@ nsStringBundleService::getStringBundle(const char *aURLSpec, // cache hit! // remove it from the list, it will later be reinserted // at the head of the list - PR_REMOVE_LINK((PRCList*)cacheEntry); + cacheEntry->remove(); } else { @@ -633,8 +625,7 @@ nsStringBundleService::getStringBundle(const char *aURLSpec, // at this point the cacheEntry should exist in the hashtable, // but is not in the LRU cache. // put the cache entry at the front of the list - - PR_INSERT_LINK((PRCList *)cacheEntry, &mBundleCache); + mBundleCache.insertFront(cacheEntry); // finally, return the value *aResult = cacheEntry->mBundle; @@ -654,12 +645,12 @@ nsStringBundleService::insertIntoCache(nsIStringBundle* aBundle, void *cacheEntryArena; PL_ARENA_ALLOCATE(cacheEntryArena, &mCacheEntryPool, sizeof(bundleCacheEntry_t)); - cacheEntry = (bundleCacheEntry_t*)cacheEntryArena; + cacheEntry = new (cacheEntryArena) bundleCacheEntry_t(); } else { // cache is full // take the last entry in the list, and recycle it. - cacheEntry = (bundleCacheEntry_t*)PR_LIST_TAIL(&mBundleCache); + cacheEntry = mBundleCache.getLast(); // remove it from the hash table and linked list NS_ASSERTION(mBundleMap.Exists(cacheEntry->mHashKey), @@ -670,7 +661,7 @@ nsStringBundleService::insertIntoCache(nsIStringBundle* aBundle, aHashKey->GetString()).get()); #endif mBundleMap.Remove(cacheEntry->mHashKey); - PR_REMOVE_LINK((PRCList*)cacheEntry); + cacheEntry->remove(); // free up excess memory recycleEntry(cacheEntry); diff --git a/intl/strres/src/nsStringBundleService.h b/intl/strres/src/nsStringBundleService.h index c6e6876cd03..c65ca5d4fc9 100644 --- a/intl/strres/src/nsStringBundleService.h +++ b/intl/strres/src/nsStringBundleService.h @@ -6,7 +6,6 @@ #ifndef nsStringBundleService_h__ #define nsStringBundleService_h__ -#include "prclist.h" #include "plarena.h" #include "nsCOMPtr.h" @@ -18,6 +17,8 @@ #include "nsIErrorService.h" #include "nsIStringBundleOverride.h" +#include "mozilla/LinkedList.h" + struct bundleCacheEntry_t; class nsStringBundleService : public nsIStringBundleService, @@ -48,7 +49,7 @@ private: static void recycleEntry(bundleCacheEntry_t*); nsHashtable mBundleMap; - PRCList mBundleCache; + mozilla::LinkedList mBundleCache; PLArenaPool mCacheEntryPool; nsCOMPtr mErrorService; From 48fb219433a5d8cebd9e764db46493c760cccbd4 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Fri, 19 Oct 2012 12:24:12 -0400 Subject: [PATCH 31/77] Bug 803666 - convert nsDocLoader's outstanding status list to use mozilla::LinkedList; r=smaug --- uriloader/base/nsDocLoader.cpp | 84 ++++++++-------------------------- uriloader/base/nsDocLoader.h | 57 +++++++++++++++++++++-- 2 files changed, 73 insertions(+), 68 deletions(-) diff --git a/uriloader/base/nsDocLoader.cpp b/uriloader/base/nsDocLoader.cpp index 9f81cacfb32..4a1f7474127 100644 --- a/uriloader/base/nsDocLoader.cpp +++ b/uriloader/base/nsDocLoader.cpp @@ -62,64 +62,21 @@ void GetURIStringFromRequest(nsIRequest* request, nsACString &name) } #endif /* DEBUG */ -struct nsStatusInfo : public PRCList -{ - nsString mStatusMessage; - nsresult mStatusCode; - // Weak mRequest is ok; we'll be told if it decides to go away. - nsIRequest * const mRequest; - - nsStatusInfo(nsIRequest *aRequest) : - mRequest(aRequest) - { - MOZ_COUNT_CTOR(nsStatusInfo); - PR_INIT_CLIST(this); - } - ~nsStatusInfo() - { - MOZ_COUNT_DTOR(nsStatusInfo); - PR_REMOVE_LINK(this); - } -}; - -struct nsRequestInfo : public PLDHashEntryHdr -{ - nsRequestInfo(const void *key) - : mKey(key), mCurrentProgress(0), mMaxProgress(0), mUploading(false) - , mLastStatus(nullptr) - { - MOZ_COUNT_CTOR(nsRequestInfo); - } - - ~nsRequestInfo() - { - MOZ_COUNT_DTOR(nsRequestInfo); - } - - nsIRequest* Request() { - return static_cast(const_cast(mKey)); - } - - const void* mKey; // Must be first for the pldhash stubs to work - int64_t mCurrentProgress; - int64_t mMaxProgress; - bool mUploading; - - nsAutoPtr mLastStatus; -}; -static bool -RequestInfoHashInitEntry(PLDHashTable *table, PLDHashEntryHdr *entry, - const void *key) +bool +nsDocLoader::RequestInfoHashInitEntry(PLDHashTable* table, + PLDHashEntryHdr* entry, + const void* key) { // Initialize the entry with placement new new (entry) nsRequestInfo(key); return true; } -static void -RequestInfoHashClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry) +void +nsDocLoader::RequestInfoHashClearEntry(PLDHashTable* table, + PLDHashEntryHdr* entry) { nsRequestInfo* info = static_cast(entry); info->~nsRequestInfo(); @@ -178,8 +135,6 @@ nsDocLoader::nsDocLoader() ClearInternalProgress(); - PR_INIT_CLIST(&mStatusInfoList); - PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, ("DocLoader:%p: created.\n", this)); } @@ -892,9 +847,8 @@ void nsDocLoader::doStopURLLoad(nsIRequest *request, nsresult aStatus) // Fire a status change message for the most recent unfinished // request to make sure that the displayed status is not outdated. - if (!PR_CLIST_IS_EMPTY(&mStatusInfoList)) { - nsStatusInfo* statusInfo = - static_cast(PR_LIST_HEAD(&mStatusInfoList)); + if (!mStatusInfoList.isEmpty()) { + nsStatusInfo* statusInfo = mStatusInfoList.getFirst(); FireOnStatusChange(this, statusInfo->mRequest, statusInfo->mStatusCode, statusInfo->mStatusMessage.get()); @@ -1179,12 +1133,12 @@ NS_IMETHODIMP nsDocLoader::OnStatus(nsIRequest* aRequest, nsISupports* ctxt, } else { // We're going to move it to the front of the list, so remove // it from wherever it is now. - PR_REMOVE_LINK(info->mLastStatus); + info->mLastStatus->remove(); } info->mLastStatus->mStatusMessage = msg; info->mLastStatus->mStatusCode = aStatus; // Put the info at the front of the list - PR_INSERT_LINK(info->mLastStatus, &mStatusInfoList); + mStatusInfoList.insertFront(info->mLastStatus); } FireOnStatusChange(this, aRequest, aStatus, msg); } @@ -1535,10 +1489,10 @@ void nsDocLoader::RemoveRequestInfo(nsIRequest *aRequest) PL_DHashTableOperate(&mRequestInfoHash, aRequest, PL_DHASH_REMOVE); } -nsRequestInfo * nsDocLoader::GetRequestInfo(nsIRequest *aRequest) +nsDocLoader::nsRequestInfo* nsDocLoader::GetRequestInfo(nsIRequest* aRequest) { - nsRequestInfo *info = - static_cast + nsRequestInfo* info = + static_cast (PL_DHashTableOperate(&mRequestInfoHash, aRequest, PL_DHASH_LOOKUP)); @@ -1574,12 +1528,12 @@ void nsDocLoader::ClearRequestInfoHash(void) } // PLDHashTable enumeration callback that calculates the max progress. -static PLDHashOperator -CalcMaxProgressCallback(PLDHashTable *table, PLDHashEntryHdr *hdr, - uint32_t number, void *arg) +PLDHashOperator +nsDocLoader::CalcMaxProgressCallback(PLDHashTable* table, PLDHashEntryHdr* hdr, + uint32_t number, void* arg) { - const nsRequestInfo *info = static_cast(hdr); - int64_t *max = static_cast(arg); + const nsRequestInfo* info = static_cast(hdr); + int64_t* max = static_cast(arg); if (info->mMaxProgress < info->mCurrentProgress) { *max = int64_t(-1); diff --git a/uriloader/base/nsDocLoader.h b/uriloader/base/nsDocLoader.h index be0507540c7..66f9d3d9b85 100644 --- a/uriloader/base/nsDocLoader.h +++ b/uriloader/base/nsDocLoader.h @@ -27,10 +27,10 @@ #include "nsISupportsPriority.h" #include "nsCOMPtr.h" #include "pldhash.h" -#include "prclist.h" #include "nsAutoPtr.h" -struct nsRequestInfo; +#include "mozilla/LinkedList.h" + struct nsListenerInfo; /**************************************************************************** @@ -194,6 +194,54 @@ protected: } protected: + struct nsStatusInfo : public mozilla::LinkedListElement + { + nsString mStatusMessage; + nsresult mStatusCode; + // Weak mRequest is ok; we'll be told if it decides to go away. + nsIRequest * const mRequest; + + nsStatusInfo(nsIRequest* aRequest) : + mRequest(aRequest) + { + MOZ_COUNT_CTOR(nsStatusInfo); + } + ~nsStatusInfo() + { + MOZ_COUNT_DTOR(nsStatusInfo); + } + }; + + struct nsRequestInfo : public PLDHashEntryHdr + { + nsRequestInfo(const void* key) + : mKey(key), mCurrentProgress(0), mMaxProgress(0), mUploading(false) + , mLastStatus(nullptr) + { + MOZ_COUNT_CTOR(nsRequestInfo); + } + + ~nsRequestInfo() + { + MOZ_COUNT_DTOR(nsRequestInfo); + } + + nsIRequest* Request() { + return static_cast(const_cast(mKey)); + } + + const void* mKey; // Must be first for the pldhash stubs to work + int64_t mCurrentProgress; + int64_t mMaxProgress; + bool mUploading; + + nsAutoPtr mLastStatus; + }; + + static bool RequestInfoHashInitEntry(PLDHashTable* table, PLDHashEntryHdr* entry, + const void* key); + static void RequestInfoHashClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry); + // IMPORTANT: The ownership implicit in the following member // variables has been explicitly checked and set using nsCOMPtr // for owning pointers and raw COM interface pointers for weak @@ -223,7 +271,7 @@ protected: PLDHashTable mRequestInfoHash; int64_t mCompletedTotalProgress; - PRCList mStatusInfoList; + mozilla::LinkedList mStatusInfoList; /* * This flag indicates that the loader is loading a document. It is set @@ -268,6 +316,9 @@ private: nsRequestInfo *GetRequestInfo(nsIRequest* aRequest); void ClearRequestInfoHash(); int64_t CalculateMaxProgress(); + static PLDHashOperator CalcMaxProgressCallback(PLDHashTable* table, + PLDHashEntryHdr* hdr, + uint32_t number, void* arg); /// void DumpChannelInfo(void); // used to clear our internal progress state between loads... From dfb7fa0bf445fb62b4a765d58bdc5ff52a347e15 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Fri, 19 Oct 2012 12:23:15 -0400 Subject: [PATCH 32/77] Bug 803669 - convert nsScannerBufferList to use mozilla::LinkedList; r=mrbkap --- parser/htmlparser/public/nsScannerString.h | 29 +++++++------ parser/htmlparser/src/nsScannerString.cpp | 49 ++++++++-------------- 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/parser/htmlparser/public/nsScannerString.h b/parser/htmlparser/public/nsScannerString.h index 75b8323e6af..ee1ef1cbbc9 100644 --- a/parser/htmlparser/public/nsScannerString.h +++ b/parser/htmlparser/public/nsScannerString.h @@ -9,7 +9,7 @@ #include "nsString.h" #include "nsUnicharUtils.h" // for nsCaseInsensitiveStringComparator -#include "prclist.h" +#include "mozilla/LinkedList.h" /** @@ -60,7 +60,7 @@ class nsScannerBufferList * of the data segment is determined by increment the |this| pointer * by 1 unit. */ - class Buffer : public PRCList + class Buffer : public mozilla::LinkedListElement { public: @@ -75,11 +75,11 @@ class nsScannerBufferList const PRUnichar* DataEnd() const { return mDataEnd; } PRUnichar* DataEnd() { return mDataEnd; } - const Buffer* Next() const { return static_cast(next); } - Buffer* Next() { return static_cast(next); } + const Buffer* Next() const { return getNext(); } + Buffer* Next() { return getNext(); } - const Buffer* Prev() const { return static_cast(prev); } - Buffer* Prev() { return static_cast(prev); } + const Buffer* Prev() const { return getPrevious(); } + Buffer* Prev() { return getPrevious(); } uint32_t DataLength() const { return mDataEnd - DataStart(); } void SetDataLength(uint32_t len) { mDataEnd = DataStart() + len; } @@ -126,23 +126,22 @@ class nsScannerBufferList nsScannerBufferList( Buffer* buf ) : mRefCnt(0) { - PR_INIT_CLIST(&mBuffers); - PR_APPEND_LINK(buf, &mBuffers); + mBuffers.insertBack(buf); } void AddRef() { ++mRefCnt; } void Release() { if (--mRefCnt == 0) delete this; } - void Append( Buffer* buf ) { PR_APPEND_LINK(buf, &mBuffers); } - void InsertAfter( Buffer* buf, Buffer* prev ) { PR_INSERT_AFTER(buf, prev); } + void Append( Buffer* buf ) { mBuffers.insertBack(buf); } + void InsertAfter( Buffer* buf, Buffer* prev ) { prev->setNext(buf); } void SplitBuffer( const Position& ); void DiscardUnreferencedPrefix( Buffer* ); - Buffer* Head() { return static_cast(PR_LIST_HEAD(&mBuffers)); } - const Buffer* Head() const { return static_cast(PR_LIST_HEAD(&mBuffers)); } + Buffer* Head() { return mBuffers.getFirst(); } + const Buffer* Head() const { return mBuffers.getFirst(); } - Buffer* Tail() { return static_cast(PR_LIST_TAIL(&mBuffers)); } - const Buffer* Tail() const { return static_cast(PR_LIST_TAIL(&mBuffers)); } + Buffer* Tail() { return mBuffers.getLast(); } + const Buffer* Tail() const { return mBuffers.getLast(); } private: @@ -152,7 +151,7 @@ class nsScannerBufferList void ReleaseAll(); int32_t mRefCnt; - PRCList mBuffers; + mozilla::LinkedList mBuffers; }; diff --git a/parser/htmlparser/src/nsScannerString.cpp b/parser/htmlparser/src/nsScannerString.cpp index 29969a63ad3..53200514386 100644 --- a/parser/htmlparser/src/nsScannerString.cpp +++ b/parser/htmlparser/src/nsScannerString.cpp @@ -19,25 +19,13 @@ nsScannerBufferList::Buffer* nsScannerBufferList::AllocBufferFromString( const nsAString& aString ) { uint32_t len = aString.Length(); + Buffer* buf = AllocBuffer(len); - if (len > MAX_CAPACITY) - return nullptr; - - Buffer* buf = (Buffer*) malloc(sizeof(Buffer) + (len + 1) * sizeof(PRUnichar)); if (buf) { - // leave PRCList members of Buffer uninitialized - - buf->mUsageCount = 0; - buf->mDataEnd = buf->DataStart() + len; - nsAString::const_iterator source; aString.BeginReading(source); nsCharTraits::copy(buf->DataStart(), source.get(), len); - - // XXX null terminate. this shouldn't be required, but we do it because - // nsScanner erroneously thinks it can dereference DataEnd :-( - *buf->mDataEnd = PRUnichar(0); } return buf; } @@ -48,30 +36,29 @@ nsScannerBufferList::AllocBuffer( uint32_t capacity ) if (capacity > MAX_CAPACITY) return nullptr; - Buffer* buf = (Buffer*) malloc(sizeof(Buffer) + (capacity + 1) * sizeof(PRUnichar)); - if (buf) - { - // leave PRCList members of Buffer uninitialized + void* ptr = malloc(sizeof(Buffer) + (capacity + 1) * sizeof(PRUnichar)); + if (!ptr) + return nullptr; - buf->mUsageCount = 0; - buf->mDataEnd = buf->DataStart() + capacity; + Buffer* buf = new (ptr) Buffer(); - // XXX null terminate. this shouldn't be required, but we do it because - // nsScanner erroneously thinks it can dereference DataEnd :-( - *buf->mDataEnd = PRUnichar(0); - } + buf->mUsageCount = 0; + buf->mDataEnd = buf->DataStart() + capacity; + + // XXX null terminate. this shouldn't be required, but we do it because + // nsScanner erroneously thinks it can dereference DataEnd :-( + *buf->mDataEnd = PRUnichar(0); return buf; } void nsScannerBufferList::ReleaseAll() { - while (!PR_CLIST_IS_EMPTY(&mBuffers)) + while (!mBuffers.isEmpty()) { - PRCList* node = PR_LIST_HEAD(&mBuffers); - PR_REMOVE_LINK(node); + Buffer* node = mBuffers.popFirst(); //printf(">>> freeing buffer @%p\n", node); - free(static_cast(node)); + free(node); } } @@ -106,10 +93,10 @@ nsScannerBufferList::DiscardUnreferencedPrefix( Buffer* aBuf ) { if (aBuf == Head()) { - while (!PR_CLIST_IS_EMPTY(&mBuffers) && !Head()->IsInUse()) + while (!mBuffers.isEmpty() && !Head()->IsInUse()) { Buffer* buffer = Head(); - PR_REMOVE_LINK(buffer); + buffer->remove(); free(buffer); } } @@ -276,7 +263,7 @@ nsScannerSubstring::GetNextFragment( nsScannerFragment& frag ) const if (frag.mBuffer == mEnd.mBuffer) return false; - frag.mBuffer = static_cast(PR_NEXT_LINK(frag.mBuffer)); + frag.mBuffer = frag.mBuffer->getNext(); if (frag.mBuffer == mStart.mBuffer) frag.mFragmentStart = mStart.mPosition; @@ -298,7 +285,7 @@ nsScannerSubstring::GetPrevFragment( nsScannerFragment& frag ) const if (frag.mBuffer == mStart.mBuffer) return false; - frag.mBuffer = static_cast(PR_PREV_LINK(frag.mBuffer)); + frag.mBuffer = frag.mBuffer->getPrevious(); if (frag.mBuffer == mStart.mBuffer) frag.mFragmentStart = mStart.mPosition; From 05fa391d0c22d87588ef117a0bbf963777e14492 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 11:47:07 -0500 Subject: [PATCH 33/77] Bug 809199 - Move the interpolate function from ViewportMetrics to ImmutableViewportMetrics. r=Cwiiis --- .../base/gfx/ImmutableViewportMetrics.java | 45 +++++++++++++++++++ mobile/android/base/gfx/PointUtils.java | 9 ---- mobile/android/base/gfx/RectUtils.java | 11 ----- mobile/android/base/gfx/ViewportMetrics.java | 14 ------ mobile/android/base/ui/PanZoomController.java | 14 +++--- 5 files changed, 52 insertions(+), 41 deletions(-) diff --git a/mobile/android/base/gfx/ImmutableViewportMetrics.java b/mobile/android/base/gfx/ImmutableViewportMetrics.java index 5403c80ff42..f40f2b65f5d 100644 --- a/mobile/android/base/gfx/ImmutableViewportMetrics.java +++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java @@ -5,6 +5,8 @@ package org.mozilla.gecko.gfx; +import org.mozilla.gecko.util.FloatUtils; + import android.graphics.PointF; import android.graphics.RectF; @@ -53,6 +55,27 @@ public class ImmutableViewportMetrics { zoomFactor = m.getZoomFactor(); } + private ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop, + float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft, + float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom, + float aViewportRectLeft, float aViewportRectTop, float aViewportRectRight, + float aViewportRectBottom, float aZoomFactor) + { + pageRectLeft = aPageRectLeft; + pageRectTop = aPageRectTop; + pageRectRight = aPageRectRight; + pageRectBottom = aPageRectBottom; + cssPageRectLeft = aCssPageRectLeft; + cssPageRectTop = aCssPageRectTop; + cssPageRectRight = aCssPageRectRight; + cssPageRectBottom = aCssPageRectBottom; + viewportRectLeft = aViewportRectLeft; + viewportRectTop = aViewportRectTop; + viewportRectRight = aViewportRectRight; + viewportRectBottom = aViewportRectBottom; + zoomFactor = aZoomFactor; + } + public float getWidth() { return viewportRectRight - viewportRectLeft; } @@ -98,6 +121,28 @@ public class ImmutableViewportMetrics { return new RectF(cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom); } + /* + * Returns the viewport metrics that represent a linear transition between "this" and "to" at + * time "t", which is on the scale [0, 1). This function interpolates all values stored in + * the viewport metrics. + */ + public ImmutableViewportMetrics interpolate(ImmutableViewportMetrics to, float t) { + return new ImmutableViewportMetrics( + FloatUtils.interpolate(pageRectLeft, to.pageRectLeft, t), + FloatUtils.interpolate(pageRectTop, to.pageRectTop, t), + FloatUtils.interpolate(pageRectRight, to.pageRectRight, t), + FloatUtils.interpolate(pageRectBottom, to.pageRectBottom, t), + FloatUtils.interpolate(cssPageRectLeft, to.cssPageRectLeft, t), + FloatUtils.interpolate(cssPageRectTop, to.cssPageRectTop, t), + FloatUtils.interpolate(cssPageRectRight, to.cssPageRectRight, t), + FloatUtils.interpolate(cssPageRectBottom, to.cssPageRectBottom, t), + FloatUtils.interpolate(viewportRectLeft, to.viewportRectLeft, t), + FloatUtils.interpolate(viewportRectTop, to.viewportRectTop, t), + FloatUtils.interpolate(viewportRectRight, to.viewportRectRight, t), + FloatUtils.interpolate(viewportRectBottom, to.viewportRectBottom, t), + FloatUtils.interpolate(zoomFactor, to.zoomFactor, t)); + } + @Override public String toString() { return "ImmutableViewportMetrics v=(" + viewportRectLeft + "," + viewportRectTop + "," diff --git a/mobile/android/base/gfx/PointUtils.java b/mobile/android/base/gfx/PointUtils.java index e524b6dc87c..c730209e3e0 100644 --- a/mobile/android/base/gfx/PointUtils.java +++ b/mobile/android/base/gfx/PointUtils.java @@ -30,15 +30,6 @@ public final class PointUtils { return new Point(Math.round(point.x), Math.round(point.y)); } - /* Returns a new point that is a linear interpolation between start and end points. weight conrols the weighting - * of each of the original points (weight = 1 returns endPoint, weight = 0 returns startPoint) - */ - public static PointF interpolate(PointF startPoint, PointF endPoint, float weight) { - float x = FloatUtils.interpolate(startPoint.x, endPoint.x, weight); - float y = FloatUtils.interpolate(startPoint.y, endPoint.y, weight); - return new PointF(x, y); - } - /* Computes the magnitude of the given vector. */ public static float distance(PointF point) { return (float)Math.sqrt(point.x * point.x + point.y * point.y); diff --git a/mobile/android/base/gfx/RectUtils.java b/mobile/android/base/gfx/RectUtils.java index 63589ed0ae6..81657f003f0 100644 --- a/mobile/android/base/gfx/RectUtils.java +++ b/mobile/android/base/gfx/RectUtils.java @@ -103,17 +103,6 @@ public final class RectUtils { return new PointF(rect.left, rect.top); } - /* - * Returns the rect that represents a linear transition between `from` and `to` at time `t`, - * which is on the scale [0, 1). - */ - public static RectF interpolate(RectF from, RectF to, float t) { - return new RectF(FloatUtils.interpolate(from.left, to.left, t), - FloatUtils.interpolate(from.top, to.top, t), - FloatUtils.interpolate(from.right, to.right, t), - FloatUtils.interpolate(from.bottom, to.bottom, t)); - } - public static boolean fuzzyEquals(RectF a, RectF b) { if (a == null && b == null) return true; diff --git a/mobile/android/base/gfx/ViewportMetrics.java b/mobile/android/base/gfx/ViewportMetrics.java index 161bd15b7fe..885724a6d9b 100644 --- a/mobile/android/base/gfx/ViewportMetrics.java +++ b/mobile/android/base/gfx/ViewportMetrics.java @@ -178,20 +178,6 @@ public class ViewportMetrics { mZoomFactor = newZoomFactor; } - /* - * Returns the viewport metrics that represent a linear transition between `from` and `to` at - * time `t`, which is on the scale [0, 1). This function interpolates the viewport rect, the - * page size, the offset, and the zoom factor. - */ - public ViewportMetrics interpolate(ViewportMetrics to, float t) { - ViewportMetrics result = new ViewportMetrics(this); - result.mPageRect = RectUtils.interpolate(mPageRect, to.mPageRect, t); - result.mCssPageRect = RectUtils.interpolate(mCssPageRect, to.mCssPageRect, t); - result.mZoomFactor = FloatUtils.interpolate(mZoomFactor, to.mZoomFactor, t); - result.mViewportRect = RectUtils.interpolate(mViewportRect, to.mViewportRect, t); - return result; - } - public boolean fuzzyEquals(ViewportMetrics other) { return RectUtils.fuzzyEquals(mPageRect, other.mPageRect) && RectUtils.fuzzyEquals(mCssPageRect, other.mCssPageRect) diff --git a/mobile/android/base/ui/PanZoomController.java b/mobile/android/base/ui/PanZoomController.java index 0055be0b782..7ff3e5892df 100644 --- a/mobile/android/base/ui/PanZoomController.java +++ b/mobile/android/base/ui/PanZoomController.java @@ -622,12 +622,12 @@ public class PanZoomController * The viewport metrics that represent the start and end of the bounce-back animation, * respectively. */ - private ViewportMetrics mBounceStartMetrics; - private ViewportMetrics mBounceEndMetrics; + private ImmutableViewportMetrics mBounceStartMetrics; + private ImmutableViewportMetrics mBounceEndMetrics; BounceRunnable(ViewportMetrics startMetrics, ViewportMetrics endMetrics) { - mBounceStartMetrics = startMetrics; - mBounceEndMetrics = endMetrics; + mBounceStartMetrics = new ImmutableViewportMetrics(startMetrics); + mBounceEndMetrics = new ImmutableViewportMetrics(endMetrics); } protected void animateFrame() { @@ -657,8 +657,8 @@ public class PanZoomController private void advanceBounce() { synchronized (mTarget.getLock()) { float t = easeOut(mBounceFrame * Axis.MS_PER_FRAME / 256f); - ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t); - mTarget.setViewportMetrics(newMetrics); + ImmutableViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t); + mTarget.setViewportMetrics(new ViewportMetrics(newMetrics)); mBounceFrame++; } } @@ -666,7 +666,7 @@ public class PanZoomController /* Concludes a bounce animation and snaps the viewport into place. */ private void finishBounce() { synchronized (mTarget.getLock()) { - mTarget.setViewportMetrics(mBounceEndMetrics); + mTarget.setViewportMetrics(new ViewportMetrics(mBounceEndMetrics)); mBounceFrame = -1; } } From ad50848c5934e068e9d5f01c444a8db376d59c63 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 11:47:07 -0500 Subject: [PATCH 34/77] Bug 809199 - Modify methods in PanZoomTarget to take ImmutableViewportMetrics instead of ViewportMetrics. r=Cwiiis --- mobile/android/base/gfx/GeckoLayerClient.java | 15 ++++----- .../base/gfx/ImmutableViewportMetrics.java | 16 ++++++++++ mobile/android/base/gfx/ViewportMetrics.java | 7 ---- mobile/android/base/ui/PanZoomController.java | 32 +++++++++---------- mobile/android/base/ui/PanZoomTarget.java | 5 ++- 5 files changed, 41 insertions(+), 34 deletions(-) diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index 141b1795aa7..751c6b40b31 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -336,7 +336,7 @@ public class GeckoLayerClient mGeckoViewport = newMetrics; } }); - setViewportMetrics(newMetrics, type == ViewportMessageType.UPDATE); + setViewportMetrics(new ImmutableViewportMetrics(newMetrics), type == ViewportMessageType.UPDATE); mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null); } return mDisplayPort; @@ -483,7 +483,7 @@ public class GeckoLayerClient mGeckoViewport = currentMetrics; } }); - setViewportMetrics(currentMetrics); + setViewportMetrics(new ImmutableViewportMetrics(currentMetrics)); Tab tab = Tabs.getInstance().getSelectedTab(); mView.setCheckerboardColor(tab.getCheckerboardColor()); @@ -662,13 +662,12 @@ public class GeckoLayerClient } /** Implementation of PanZoomTarget */ - public void setAnimationTarget(ViewportMetrics viewport) { + public void setAnimationTarget(ImmutableViewportMetrics metrics) { if (mGeckoIsReady) { // We know what the final viewport of the animation is going to be, so // immediately request a draw of that area by setting the display port // accordingly. This way we should have the content pre-rendered by the // time the animation is done. - ImmutableViewportMetrics metrics = new ImmutableViewportMetrics(viewport); DisplayPortMetrics displayPort = DisplayPortCalculator.calculate(metrics, null); adjustViewport(displayPort); } @@ -677,12 +676,12 @@ public class GeckoLayerClient /** Implementation of PanZoomTarget * You must hold the monitor while calling this. */ - public void setViewportMetrics(ViewportMetrics viewport) { - setViewportMetrics(viewport, true); + public void setViewportMetrics(ImmutableViewportMetrics metrics) { + setViewportMetrics(metrics, true); } - private void setViewportMetrics(ViewportMetrics viewport, boolean notifyGecko) { - mViewportMetrics = new ImmutableViewportMetrics(viewport); + private void setViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko) { + mViewportMetrics = metrics; mView.requestRender(); if (notifyGecko && mGeckoIsReady) { geometryChanged(); diff --git a/mobile/android/base/gfx/ImmutableViewportMetrics.java b/mobile/android/base/gfx/ImmutableViewportMetrics.java index f40f2b65f5d..e01f5bfdcd2 100644 --- a/mobile/android/base/gfx/ImmutableViewportMetrics.java +++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java @@ -143,6 +143,22 @@ public class ImmutableViewportMetrics { FloatUtils.interpolate(zoomFactor, to.zoomFactor, t)); } + public boolean fuzzyEquals(ImmutableViewportMetrics other) { + return FloatUtils.fuzzyEquals(pageRectLeft, other.pageRectLeft) + && FloatUtils.fuzzyEquals(pageRectTop, other.pageRectTop) + && FloatUtils.fuzzyEquals(pageRectRight, other.pageRectRight) + && FloatUtils.fuzzyEquals(pageRectBottom, other.pageRectBottom) + && FloatUtils.fuzzyEquals(cssPageRectLeft, other.cssPageRectLeft) + && FloatUtils.fuzzyEquals(cssPageRectTop, other.cssPageRectTop) + && FloatUtils.fuzzyEquals(cssPageRectRight, other.cssPageRectRight) + && FloatUtils.fuzzyEquals(cssPageRectBottom, other.cssPageRectBottom) + && FloatUtils.fuzzyEquals(viewportRectLeft, other.viewportRectLeft) + && FloatUtils.fuzzyEquals(viewportRectTop, other.viewportRectTop) + && FloatUtils.fuzzyEquals(viewportRectRight, other.viewportRectRight) + && FloatUtils.fuzzyEquals(viewportRectBottom, other.viewportRectBottom) + && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor); + } + @Override public String toString() { return "ImmutableViewportMetrics v=(" + viewportRectLeft + "," + viewportRectTop + "," diff --git a/mobile/android/base/gfx/ViewportMetrics.java b/mobile/android/base/gfx/ViewportMetrics.java index 885724a6d9b..7832a7520e7 100644 --- a/mobile/android/base/gfx/ViewportMetrics.java +++ b/mobile/android/base/gfx/ViewportMetrics.java @@ -178,13 +178,6 @@ public class ViewportMetrics { mZoomFactor = newZoomFactor; } - public boolean fuzzyEquals(ViewportMetrics other) { - return RectUtils.fuzzyEquals(mPageRect, other.mPageRect) - && RectUtils.fuzzyEquals(mCssPageRect, other.mCssPageRect) - && RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect) - && FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor); - } - public String toJSON() { // Round off height and width. Since the height and width are the size of the screen, it // makes no sense to send non-integer coordinates to Gecko. diff --git a/mobile/android/base/ui/PanZoomController.java b/mobile/android/base/ui/PanZoomController.java index 7ff3e5892df..3c419965398 100644 --- a/mobile/android/base/ui/PanZoomController.java +++ b/mobile/android/base/ui/PanZoomController.java @@ -259,8 +259,8 @@ public class PanZoomController public void pageRectUpdated() { if (mState == PanZoomState.NOTHING) { synchronized (mTarget.getLock()) { - ViewportMetrics validated = getValidViewportMetrics(); - if (! getMutableMetrics().fuzzyEquals(validated)) { + ImmutableViewportMetrics validated = getValidViewportMetrics(); + if (!getMetrics().fuzzyEquals(validated)) { // page size changed such that we are now in overscroll. snap to the // the nearest valid viewport mTarget.setViewportMetrics(validated); @@ -491,7 +491,7 @@ public class PanZoomController origin.offset(point.x, point.y); viewportMetrics.setOrigin(origin); - mTarget.setViewportMetrics(viewportMetrics); + mTarget.setViewportMetrics(new ImmutableViewportMetrics(viewportMetrics)); } private void fling() { @@ -507,10 +507,10 @@ public class PanZoomController } /* Performs a bounce-back animation to the given viewport metrics. */ - private void bounce(ViewportMetrics metrics) { + private void bounce(ImmutableViewportMetrics metrics) { stopAnimationTimer(); - ViewportMetrics bounceStartMetrics = getMutableMetrics(); + ImmutableViewportMetrics bounceStartMetrics = getMetrics(); if (bounceStartMetrics.fuzzyEquals(metrics)) { setState(PanZoomState.NOTHING); return; @@ -625,9 +625,9 @@ public class PanZoomController private ImmutableViewportMetrics mBounceStartMetrics; private ImmutableViewportMetrics mBounceEndMetrics; - BounceRunnable(ViewportMetrics startMetrics, ViewportMetrics endMetrics) { - mBounceStartMetrics = new ImmutableViewportMetrics(startMetrics); - mBounceEndMetrics = new ImmutableViewportMetrics(endMetrics); + BounceRunnable(ImmutableViewportMetrics startMetrics, ImmutableViewportMetrics endMetrics) { + mBounceStartMetrics = startMetrics; + mBounceEndMetrics = endMetrics; } protected void animateFrame() { @@ -658,7 +658,7 @@ public class PanZoomController synchronized (mTarget.getLock()) { float t = easeOut(mBounceFrame * Axis.MS_PER_FRAME / 256f); ImmutableViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t); - mTarget.setViewportMetrics(new ViewportMetrics(newMetrics)); + mTarget.setViewportMetrics(newMetrics); mBounceFrame++; } } @@ -666,7 +666,7 @@ public class PanZoomController /* Concludes a bounce animation and snaps the viewport into place. */ private void finishBounce() { synchronized (mTarget.getLock()) { - mTarget.setViewportMetrics(new ViewportMetrics(mBounceEndMetrics)); + mTarget.setViewportMetrics(mBounceEndMetrics); mBounceFrame = -1; } } @@ -731,11 +731,11 @@ public class PanZoomController } /* Returns the nearest viewport metrics with no overscroll visible. */ - private ViewportMetrics getValidViewportMetrics() { + private ImmutableViewportMetrics getValidViewportMetrics() { return getValidViewportMetrics(getMutableMetrics()); } - private ViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) { + private ImmutableViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) { /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */ float zoomFactor = viewportMetrics.getZoomFactor(); RectF pageRect = viewportMetrics.getPageRect(); @@ -791,7 +791,7 @@ public class PanZoomController /* Now we pan to the right origin. */ viewportMetrics.setViewport(viewportMetrics.getClampedViewport()); - return viewportMetrics; + return new ImmutableViewportMetrics(viewportMetrics); } private class AxisX extends Axis { @@ -937,7 +937,7 @@ public class PanZoomController private void scaleWithFocus(float zoomFactor, PointF focus) { ViewportMetrics viewportMetrics = getMutableMetrics(); viewportMetrics.scaleTo(zoomFactor, focus); - mTarget.setViewportMetrics(viewportMetrics); + mTarget.setViewportMetrics(new ImmutableViewportMetrics(viewportMetrics)); } public boolean getRedrawHint() { @@ -1049,9 +1049,9 @@ public class PanZoomController // 2. now run getValidViewportMetrics on it, so that the target viewport is // clamped down to prevent overscroll, over-zoom, and other bad conditions. - finalMetrics = getValidViewportMetrics(finalMetrics); + ImmutableViewportMetrics finalValidMetrics = getValidViewportMetrics(finalMetrics); - bounce(finalMetrics); + bounce(finalValidMetrics); return true; } diff --git a/mobile/android/base/ui/PanZoomTarget.java b/mobile/android/base/ui/PanZoomTarget.java index ac4b6c48e9e..ef8a641bf5e 100644 --- a/mobile/android/base/ui/PanZoomTarget.java +++ b/mobile/android/base/ui/PanZoomTarget.java @@ -7,7 +7,6 @@ package org.mozilla.gecko.ui; import org.mozilla.gecko.ZoomConstraints; import org.mozilla.gecko.gfx.ImmutableViewportMetrics; -import org.mozilla.gecko.gfx.ViewportMetrics; import android.graphics.PointF; @@ -15,8 +14,8 @@ public interface PanZoomTarget { public ImmutableViewportMetrics getViewportMetrics(); public ZoomConstraints getZoomConstraints(); - public void setAnimationTarget(ViewportMetrics viewport); - public void setViewportMetrics(ViewportMetrics viewport); + public void setAnimationTarget(ImmutableViewportMetrics viewport); + public void setViewportMetrics(ImmutableViewportMetrics viewport); public void setForceRedraw(); public boolean post(Runnable action); From 46d17b95d24322ac64b97040be4edb1528f81c01 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 11:47:08 -0500 Subject: [PATCH 35/77] Bug 809199 - Add an offsetViewportBy function to ImmutableViewportMetrics. r=Cwiiis --- mobile/android/base/gfx/ImmutableViewportMetrics.java | 11 +++++++++++ mobile/android/base/ui/PanZoomController.java | 8 ++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/mobile/android/base/gfx/ImmutableViewportMetrics.java b/mobile/android/base/gfx/ImmutableViewportMetrics.java index e01f5bfdcd2..4081e66b0ee 100644 --- a/mobile/android/base/gfx/ImmutableViewportMetrics.java +++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java @@ -143,6 +143,17 @@ public class ImmutableViewportMetrics { FloatUtils.interpolate(zoomFactor, to.zoomFactor, t)); } + public ImmutableViewportMetrics offsetViewportBy(PointF delta) { + return new ImmutableViewportMetrics( + pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, + viewportRectLeft + delta.x, + viewportRectTop + delta.y, + viewportRectRight + delta.x, + viewportRectBottom + delta.y, + zoomFactor); + } + public boolean fuzzyEquals(ImmutableViewportMetrics other) { return FloatUtils.fuzzyEquals(pageRectLeft, other.pageRectLeft) && FloatUtils.fuzzyEquals(pageRectTop, other.pageRectTop) diff --git a/mobile/android/base/ui/PanZoomController.java b/mobile/android/base/ui/PanZoomController.java index 3c419965398..18aaeb8a88a 100644 --- a/mobile/android/base/ui/PanZoomController.java +++ b/mobile/android/base/ui/PanZoomController.java @@ -486,12 +486,8 @@ public class PanZoomController } private void scrollBy(PointF point) { - ViewportMetrics viewportMetrics = getMutableMetrics(); - PointF origin = viewportMetrics.getOrigin(); - origin.offset(point.x, point.y); - viewportMetrics.setOrigin(origin); - - mTarget.setViewportMetrics(new ImmutableViewportMetrics(viewportMetrics)); + ImmutableViewportMetrics scrolled = getMetrics().offsetViewportBy(point); + mTarget.setViewportMetrics(scrolled); } private void fling() { From 7d92cc7bef6934038b445be8817a824d17b3dbf2 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 11:47:08 -0500 Subject: [PATCH 36/77] Bug 809199 - Eliminate use of ViewportMetrics from PanZoomController. r=Cwiiis --- mobile/android/base/gfx/GeckoLayerClient.java | 4 +- .../base/gfx/ImmutableViewportMetrics.java | 57 +++++++++++++++++-- mobile/android/base/gfx/ViewportMetrics.java | 39 ------------- mobile/android/base/ui/PanZoomController.java | 38 ++++++------- 4 files changed, 70 insertions(+), 68 deletions(-) diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index 751c6b40b31..e6770f4254a 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -268,9 +268,7 @@ public class GeckoLayerClient private void adjustViewport(DisplayPortMetrics displayPort) { ImmutableViewportMetrics metrics = getViewportMetrics(); - - ViewportMetrics clampedMetrics = new ViewportMetrics(metrics); - clampedMetrics.setViewport(clampedMetrics.getClampedViewport()); + ViewportMetrics clampedMetrics = new ViewportMetrics(metrics.clamp()); if (displayPort == null) { displayPort = DisplayPortCalculator.calculate(metrics, mPanZoomController.getVelocityVector()); diff --git a/mobile/android/base/gfx/ImmutableViewportMetrics.java b/mobile/android/base/gfx/ImmutableViewportMetrics.java index 4081e66b0ee..e1facd13231 100644 --- a/mobile/android/base/gfx/ImmutableViewportMetrics.java +++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java @@ -143,14 +143,61 @@ public class ImmutableViewportMetrics { FloatUtils.interpolate(zoomFactor, to.zoomFactor, t)); } - public ImmutableViewportMetrics offsetViewportBy(PointF delta) { + public ImmutableViewportMetrics setViewportOrigin(float newOriginX, float newOriginY) { return new ImmutableViewportMetrics( pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, - viewportRectLeft + delta.x, - viewportRectTop + delta.y, - viewportRectRight + delta.x, - viewportRectBottom + delta.y, + newOriginX, newOriginY, newOriginX + getWidth(), newOriginY + getHeight(), + zoomFactor); + } + + public ImmutableViewportMetrics offsetViewportBy(PointF delta) { + return setViewportOrigin(viewportRectLeft + delta.x, viewportRectTop + delta.y); + } + + /* This will set the zoom factor and re-scale page-size and viewport offset + * accordingly. The given focus will remain at the same point on the screen + * after scaling. + */ + public ImmutableViewportMetrics scaleTo(float newZoomFactor, PointF focus) { + // cssPageRect* is invariant, since we're setting the scale factor + // here. The page rect is based on the CSS page rect. + float newPageRectLeft = cssPageRectLeft * newZoomFactor; + float newPageRectTop = cssPageRectTop * newZoomFactor; + float newPageRectRight = cssPageRectLeft + ((cssPageRectRight - cssPageRectLeft) * newZoomFactor); + float newPageRectBottom = cssPageRectTop + ((cssPageRectBottom - cssPageRectTop) * newZoomFactor); + + PointF origin = getOrigin(); + origin.offset(focus.x, focus.y); + origin = PointUtils.scale(origin, newZoomFactor / zoomFactor); + origin.offset(-focus.x, -focus.y); + + return new ImmutableViewportMetrics( + newPageRectLeft, newPageRectTop, newPageRectRight, newPageRectBottom, + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, + origin.x, origin.y, origin.x + getWidth(), origin.y + getHeight(), + newZoomFactor); + } + + /** Clamps the viewport to remain within the page rect. */ + public ImmutableViewportMetrics clamp() { + RectF newViewport = getViewport(); + + // The viewport bounds ought to never exceed the page bounds. + if (newViewport.right > pageRectRight) + newViewport.offset(pageRectRight - newViewport.right, 0); + if (newViewport.left < pageRectLeft) + newViewport.offset(pageRectLeft - newViewport.left, 0); + + if (newViewport.bottom > pageRectBottom) + newViewport.offset(0, pageRectBottom - newViewport.bottom); + if (newViewport.top < pageRectTop) + newViewport.offset(0, pageRectTop - newViewport.top); + + return new ImmutableViewportMetrics( + pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, + newViewport.left, newViewport.top, newViewport.right, newViewport.bottom, zoomFactor); } diff --git a/mobile/android/base/gfx/ViewportMetrics.java b/mobile/android/base/gfx/ViewportMetrics.java index 7832a7520e7..30f7ebf3663 100644 --- a/mobile/android/base/gfx/ViewportMetrics.java +++ b/mobile/android/base/gfx/ViewportMetrics.java @@ -103,24 +103,6 @@ public class ViewportMetrics { return RectUtils.scale(mViewportRect, 1/mZoomFactor); } - /** Returns the viewport rectangle, clamped within the page-size. */ - public RectF getClampedViewport() { - RectF clampedViewport = new RectF(mViewportRect); - - // The viewport bounds ought to never exceed the page bounds. - if (clampedViewport.right > mPageRect.right) - clampedViewport.offset(mPageRect.right - clampedViewport.right, 0); - if (clampedViewport.left < mPageRect.left) - clampedViewport.offset(mPageRect.left - clampedViewport.left, 0); - - if (clampedViewport.bottom > mPageRect.bottom) - clampedViewport.offset(0, mPageRect.bottom - clampedViewport.bottom); - if (clampedViewport.top < mPageRect.top) - clampedViewport.offset(0, mPageRect.top - clampedViewport.top); - - return clampedViewport; - } - public RectF getPageRect() { return mPageRect; } @@ -157,27 +139,6 @@ public class ViewportMetrics { mZoomFactor = zoomFactor; } - /* This will set the zoom factor and re-scale page-size and viewport offset - * accordingly. The given focus will remain at the same point on the screen - * after scaling. - */ - public void scaleTo(float newZoomFactor, PointF focus) { - // mCssPageRect is invariant, since we're setting the scale factor - // here. The page rect is based on the CSS page rect. - mPageRect = RectUtils.scale(mCssPageRect, newZoomFactor); - - float scaleFactor = newZoomFactor / mZoomFactor; - PointF origin = getOrigin(); - - origin.offset(focus.x, focus.y); - origin = PointUtils.scale(origin, scaleFactor); - origin.offset(-focus.x, -focus.y); - - setOrigin(origin); - - mZoomFactor = newZoomFactor; - } - public String toJSON() { // Round off height and width. Since the height and width are the size of the screen, it // makes no sense to send non-integer coordinates to Gecko. diff --git a/mobile/android/base/ui/PanZoomController.java b/mobile/android/base/ui/PanZoomController.java index 18aaeb8a88a..adbec52b761 100644 --- a/mobile/android/base/ui/PanZoomController.java +++ b/mobile/android/base/ui/PanZoomController.java @@ -12,7 +12,6 @@ import org.mozilla.gecko.PrefsHelper; import org.mozilla.gecko.ZoomConstraints; import org.mozilla.gecko.gfx.ImmutableViewportMetrics; import org.mozilla.gecko.gfx.PointUtils; -import org.mozilla.gecko.gfx.ViewportMetrics; import org.mozilla.gecko.util.EventDispatcher; import org.mozilla.gecko.util.FloatUtils; import org.mozilla.gecko.util.GeckoEventListener; @@ -145,10 +144,6 @@ public class PanZoomController return mTarget.getViewportMetrics(); } - private ViewportMetrics getMutableMetrics() { - return new ViewportMetrics(getMetrics()); - } - // for debugging bug 713011; it can be taken out once that is resolved. private void checkMainThread() { if (mMainThread != Thread.currentThread()) { @@ -728,12 +723,12 @@ public class PanZoomController /* Returns the nearest viewport metrics with no overscroll visible. */ private ImmutableViewportMetrics getValidViewportMetrics() { - return getValidViewportMetrics(getMutableMetrics()); + return getValidViewportMetrics(getMetrics()); } - private ImmutableViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) { + private ImmutableViewportMetrics getValidViewportMetrics(ImmutableViewportMetrics viewportMetrics) { /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */ - float zoomFactor = viewportMetrics.getZoomFactor(); + float zoomFactor = viewportMetrics.zoomFactor; RectF pageRect = viewportMetrics.getPageRect(); RectF viewport = viewportMetrics.getViewport(); @@ -778,16 +773,16 @@ public class PanZoomController // by different scale factors, we end up scrolled to the end on one axis // after applying the scale PointF center = new PointF(focusX, focusY); - viewportMetrics.scaleTo(minZoomFactor, center); + viewportMetrics = viewportMetrics.scaleTo(minZoomFactor, center); } else if (zoomFactor > maxZoomFactor) { PointF center = new PointF(viewport.width() / 2.0f, viewport.height() / 2.0f); - viewportMetrics.scaleTo(maxZoomFactor, center); + viewportMetrics = viewportMetrics.scaleTo(maxZoomFactor, center); } /* Now we pan to the right origin. */ - viewportMetrics.setViewport(viewportMetrics.getClampedViewport()); + viewportMetrics = viewportMetrics.clamp(); - return new ImmutableViewportMetrics(viewportMetrics); + return viewportMetrics; } private class AxisX extends Axis { @@ -931,9 +926,9 @@ public class PanZoomController * scale operation. You must hold the monitor while calling this. */ private void scaleWithFocus(float zoomFactor, PointF focus) { - ViewportMetrics viewportMetrics = getMutableMetrics(); - viewportMetrics.scaleTo(zoomFactor, focus); - mTarget.setViewportMetrics(new ImmutableViewportMetrics(viewportMetrics)); + ImmutableViewportMetrics viewportMetrics = getMetrics(); + viewportMetrics = viewportMetrics.scaleTo(zoomFactor, focus); + mTarget.setViewportMetrics(viewportMetrics); } public boolean getRedrawHint() { @@ -1038,16 +1033,17 @@ public class PanZoomController float finalZoom = viewport.width() / zoomToRect.width(); - ViewportMetrics finalMetrics = getMutableMetrics(); - finalMetrics.setOrigin(new PointF(zoomToRect.left * finalMetrics.getZoomFactor(), - zoomToRect.top * finalMetrics.getZoomFactor())); - finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f)); + ImmutableViewportMetrics finalMetrics = getMetrics(); + finalMetrics = finalMetrics.setViewportOrigin( + zoomToRect.left * finalMetrics.zoomFactor, + zoomToRect.top * finalMetrics.zoomFactor); + finalMetrics = finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f)); // 2. now run getValidViewportMetrics on it, so that the target viewport is // clamped down to prevent overscroll, over-zoom, and other bad conditions. - ImmutableViewportMetrics finalValidMetrics = getValidViewportMetrics(finalMetrics); + finalMetrics = getValidViewportMetrics(finalMetrics); - bounce(finalValidMetrics); + bounce(finalMetrics); return true; } From d03b60194c2a3933c54710c0bfeb7d37fb304873 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 11:47:08 -0500 Subject: [PATCH 37/77] Bug 809199 - Update createViewportEvent to take an ImmutableViewportMetrics. r=Cwiiis --- mobile/android/base/GeckoEvent.java | 11 +++++------ mobile/android/base/gfx/GeckoLayerClient.java | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/mobile/android/base/GeckoEvent.java b/mobile/android/base/GeckoEvent.java index 3e464aef4b5..c7825b3371a 100644 --- a/mobile/android/base/GeckoEvent.java +++ b/mobile/android/base/GeckoEvent.java @@ -6,7 +6,7 @@ package org.mozilla.gecko; import org.mozilla.gecko.gfx.DisplayPortMetrics; -import org.mozilla.gecko.gfx.ViewportMetrics; +import org.mozilla.gecko.gfx.ImmutableViewportMetrics; import android.content.res.Resources; import android.graphics.Point; @@ -527,14 +527,13 @@ public class GeckoEvent { return event; } - public static GeckoEvent createViewportEvent(ViewportMetrics viewport, DisplayPortMetrics displayPort) { + public static GeckoEvent createViewportEvent(ImmutableViewportMetrics metrics, DisplayPortMetrics displayPort) { GeckoEvent event = new GeckoEvent(VIEWPORT); event.mCharacters = "Viewport:Change"; - PointF origin = viewport.getOrigin(); StringBuffer sb = new StringBuffer(256); - sb.append("{ \"x\" : ").append(origin.x) - .append(", \"y\" : ").append(origin.y) - .append(", \"zoom\" : ").append(viewport.getZoomFactor()) + sb.append("{ \"x\" : ").append(metrics.viewportRectLeft) + .append(", \"y\" : ").append(metrics.viewportRectTop) + .append(", \"zoom\" : ").append(metrics.zoomFactor) .append(", \"displayPort\" :").append(displayPort.toJSON()) .append('}'); event.mCharactersExtra = sb.toString(); diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index e6770f4254a..0d9798443aa 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -268,14 +268,14 @@ public class GeckoLayerClient private void adjustViewport(DisplayPortMetrics displayPort) { ImmutableViewportMetrics metrics = getViewportMetrics(); - ViewportMetrics clampedMetrics = new ViewportMetrics(metrics.clamp()); + ImmutableViewportMetrics clampedMetrics = metrics.clamp(); if (displayPort == null) { displayPort = DisplayPortCalculator.calculate(metrics, mPanZoomController.getVelocityVector()); } mDisplayPort = displayPort; - mGeckoViewport = clampedMetrics; + mGeckoViewport = new ViewportMetrics(clampedMetrics); if (mRecordDrawTimes) { mDrawTimingQueue.add(displayPort); From 127e9827531cefb1ffdadeade0a2c37b64670c1b Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 11:47:08 -0500 Subject: [PATCH 38/77] Bug 809199 - Switch mGeckoViewport to be an ImmutableViewportMetrics instead of a ViewportMetrics. r=Cwiiis --- mobile/android/base/gfx/GeckoLayerClient.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index 0d9798443aa..fa14d1b52d2 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -58,7 +58,7 @@ public class GeckoLayerClient * to the Gecko viewport position. Note that if Gecko updates its viewport independently, * we get notified synchronously and also update this on the UI thread. */ - private ViewportMetrics mGeckoViewport; + private ImmutableViewportMetrics mGeckoViewport; /* * The viewport metrics being used to draw the current frame. This is only @@ -275,7 +275,7 @@ public class GeckoLayerClient } mDisplayPort = displayPort; - mGeckoViewport = new ViewportMetrics(clampedMetrics); + mGeckoViewport = clampedMetrics; if (mRecordDrawTimes) { mDrawTimingQueue.add(displayPort); @@ -308,15 +308,15 @@ public class GeckoLayerClient /** Viewport message handler. */ private DisplayPortMetrics handleViewportMessage(ViewportMetrics messageMetrics, ViewportMessageType type) { synchronized (this) { - final ViewportMetrics newMetrics; + ViewportMetrics metrics; ImmutableViewportMetrics oldMetrics = getViewportMetrics(); switch (type) { default: case UPDATE: - newMetrics = messageMetrics; + metrics = messageMetrics; // Keep the old viewport size - newMetrics.setSize(oldMetrics.getSize()); + metrics.setSize(oldMetrics.getSize()); abortPanZoomAnimation(); break; case PAGE_SIZE: @@ -324,17 +324,18 @@ public class GeckoLayerClient // between the rendered content (which is what Gecko tells us) // and our zoom level (which may have diverged). float scaleFactor = oldMetrics.zoomFactor / messageMetrics.getZoomFactor(); - newMetrics = new ViewportMetrics(oldMetrics); - newMetrics.setPageRect(RectUtils.scale(messageMetrics.getPageRect(), scaleFactor), messageMetrics.getCssPageRect()); + metrics = new ViewportMetrics(oldMetrics); + metrics.setPageRect(RectUtils.scale(messageMetrics.getPageRect(), scaleFactor), messageMetrics.getCssPageRect()); break; } + final ImmutableViewportMetrics newMetrics = new ImmutableViewportMetrics(metrics); post(new Runnable() { public void run() { mGeckoViewport = newMetrics; } }); - setViewportMetrics(new ImmutableViewportMetrics(newMetrics), type == ViewportMessageType.UPDATE); + setViewportMetrics(newMetrics, type == ViewportMessageType.UPDATE); mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null); } return mDisplayPort; @@ -467,21 +468,22 @@ public class GeckoLayerClient float pageLeft, float pageTop, float pageRight, float pageBottom, float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) { synchronized (this) { - final ViewportMetrics currentMetrics = new ViewportMetrics(getViewportMetrics()); + ViewportMetrics currentMetrics = new ViewportMetrics(getViewportMetrics()); currentMetrics.setOrigin(new PointF(offsetX, offsetY)); currentMetrics.setZoomFactor(zoom); currentMetrics.setPageRect(new RectF(pageLeft, pageTop, pageRight, pageBottom), new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom)); + final ImmutableViewportMetrics newMetrics = new ImmutableViewportMetrics(currentMetrics); // Since we have switched to displaying a different document, we need to update any // viewport-related state we have lying around. This includes mGeckoViewport and // mViewportMetrics. Usually this information is updated via handleViewportMessage // while we remain on the same document. post(new Runnable() { public void run() { - mGeckoViewport = currentMetrics; + mGeckoViewport = newMetrics; } }); - setViewportMetrics(new ImmutableViewportMetrics(currentMetrics)); + setViewportMetrics(newMetrics); Tab tab = Tabs.getInstance().getSelectedTab(); mView.setCheckerboardColor(tab.getCheckerboardColor()); @@ -719,9 +721,9 @@ public class GeckoLayerClient ImmutableViewportMetrics viewportMetrics = mViewportMetrics; PointF origin = viewportMetrics.getOrigin(); float zoom = viewportMetrics.zoomFactor; - ViewportMetrics geckoViewport = mGeckoViewport; + ImmutableViewportMetrics geckoViewport = mGeckoViewport; PointF geckoOrigin = geckoViewport.getOrigin(); - float geckoZoom = geckoViewport.getZoomFactor(); + float geckoZoom = geckoViewport.zoomFactor; // viewPoint + origin gives the coordinate in device pixels from the top-left corner of the page. // Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page. From cbfc94010034cd82b63d3909b6a77ee1b7188f46 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 11:47:08 -0500 Subject: [PATCH 39/77] Bug 809199 - Update the getDisplayPort JNI-called function to take an ImmutableViewportMetrics. r=Cwiiis --- mobile/android/base/gfx/GeckoLayerClient.java | 11 +++++------ widget/android/AndroidJavaWrappers.cpp | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index fa14d1b52d2..200bf6d1b36 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -306,7 +306,7 @@ public class GeckoLayerClient } /** Viewport message handler. */ - private DisplayPortMetrics handleViewportMessage(ViewportMetrics messageMetrics, ViewportMessageType type) { + private DisplayPortMetrics handleViewportMessage(ImmutableViewportMetrics messageMetrics, ViewportMessageType type) { synchronized (this) { ViewportMetrics metrics; ImmutableViewportMetrics oldMetrics = getViewportMetrics(); @@ -314,7 +314,7 @@ public class GeckoLayerClient switch (type) { default: case UPDATE: - metrics = messageMetrics; + metrics = new ViewportMetrics(messageMetrics); // Keep the old viewport size metrics.setSize(oldMetrics.getSize()); abortPanZoomAnimation(); @@ -323,7 +323,7 @@ public class GeckoLayerClient // adjust the page dimensions to account for differences in zoom // between the rendered content (which is what Gecko tells us) // and our zoom level (which may have diverged). - float scaleFactor = oldMetrics.zoomFactor / messageMetrics.getZoomFactor(); + float scaleFactor = oldMetrics.zoomFactor / messageMetrics.zoomFactor; metrics = new ViewportMetrics(oldMetrics); metrics.setPageRect(RectUtils.scale(messageMetrics.getPageRect(), scaleFactor), messageMetrics.getCssPageRect()); break; @@ -341,7 +341,7 @@ public class GeckoLayerClient return mDisplayPort; } - public DisplayPortMetrics getDisplayPort(boolean pageSizeUpdate, boolean isBrowserContentDisplayed, int tabId, ViewportMetrics metrics) { + public DisplayPortMetrics getDisplayPort(boolean pageSizeUpdate, boolean isBrowserContentDisplayed, int tabId, ImmutableViewportMetrics metrics) { Tabs tabs = Tabs.getInstance(); if (tabs.isSelectedTab(tabs.getTab(tabId)) && isBrowserContentDisplayed) { // for foreground tabs, send the viewport update unless the document @@ -353,8 +353,7 @@ public class GeckoLayerClient // when we do switch to that tab, we have the correct display port and // don't need to draw twice (once to allow the first-paint viewport to // get to java, and again once java figures out the display port). - ImmutableViewportMetrics newMetrics = new ImmutableViewportMetrics(metrics); - return DisplayPortCalculator.calculate(newMetrics, null); + return DisplayPortCalculator.calculate(metrics, null); } } diff --git a/widget/android/AndroidJavaWrappers.cpp b/widget/android/AndroidJavaWrappers.cpp index e5924deb718..7b9ac058abd 100644 --- a/widget/android/AndroidJavaWrappers.cpp +++ b/widget/android/AndroidJavaWrappers.cpp @@ -355,9 +355,9 @@ AndroidGeckoLayerClient::InitGeckoLayerClientClass(JNIEnv *jEnv) jCreateFrameMethod = getMethod("createFrame", "()Lorg/mozilla/gecko/gfx/LayerRenderer$Frame;"); jActivateProgramMethod = getMethod("activateProgram", "()V"); jDeactivateProgramMethod = getMethod("deactivateProgram", "()V"); - jGetDisplayPort = getMethod("getDisplayPort", "(ZZILorg/mozilla/gecko/gfx/ViewportMetrics;)Lorg/mozilla/gecko/gfx/DisplayPortMetrics;"); + jGetDisplayPort = getMethod("getDisplayPort", "(ZZILorg/mozilla/gecko/gfx/ImmutableViewportMetrics;)Lorg/mozilla/gecko/gfx/DisplayPortMetrics;"); - jViewportClass = GetClassGlobalRef(jEnv, "org/mozilla/gecko/gfx/ViewportMetrics"); + jViewportClass = GetClassGlobalRef(jEnv, "org/mozilla/gecko/gfx/ImmutableViewportMetrics"); jViewportCtor = GetMethodID(jEnv, jViewportClass, "", "(FFFFFFFFFFFFF)V"); jDisplayportClass = GetClassGlobalRef(jEnv, "org/mozilla/gecko/gfx/DisplayPortMetrics"); From 46436ac6d32900d1e3a1bf5fa81634555c74fa6f Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 11:47:08 -0500 Subject: [PATCH 40/77] Bug 809199 - ViewportMetrics is dead. Long live ImmutableViewportMetrics. r=Cwiiis --- mobile/android/base/Makefile.in | 1 - mobile/android/base/gfx/GeckoLayerClient.java | 31 ++-- .../base/gfx/ImmutableViewportMetrics.java | 53 +++--- mobile/android/base/gfx/ViewportMetrics.java | 175 ------------------ 4 files changed, 43 insertions(+), 217 deletions(-) delete mode 100644 mobile/android/base/gfx/ViewportMetrics.java diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 8d619d5ad18..893fbac7d30 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -171,7 +171,6 @@ FENNEC_JAVA_FILES = \ gfx/TileLayer.java \ gfx/TouchEventHandler.java \ gfx/ViewTransform.java \ - gfx/ViewportMetrics.java \ gfx/VirtualLayer.java \ ui/Axis.java \ ui/PanZoomController.java \ diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index 200bf6d1b36..3ae8eed82fe 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -117,7 +117,7 @@ public class GeckoLayerClient mForceRedraw = true; DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics(displayMetrics)); + mViewportMetrics = new ImmutableViewportMetrics(displayMetrics); mZoomConstraints = new ZoomConstraints(false); mPanZoomController = new PanZoomController(this, mEventDispatcher); @@ -192,9 +192,7 @@ public class GeckoLayerClient * result in an infinite loop. */ void setViewportSize(FloatSize size) { - ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); - viewportMetrics.setSize(size); - mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); + mViewportMetrics = mViewportMetrics.setViewportSize(size); if (mGeckoIsReady) { // here we send gecko a resize message. The code in browser.js is responsible for @@ -251,9 +249,7 @@ public class GeckoLayerClient if (mViewportMetrics.getCssPageRect().equals(cssRect)) return; - ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); - viewportMetrics.setPageRect(rect, cssRect); - mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); + mViewportMetrics = mViewportMetrics.setPageRect(rect, cssRect); // Page size is owned by the layer client, so no need to notify it of // this change. @@ -308,15 +304,14 @@ public class GeckoLayerClient /** Viewport message handler. */ private DisplayPortMetrics handleViewportMessage(ImmutableViewportMetrics messageMetrics, ViewportMessageType type) { synchronized (this) { - ViewportMetrics metrics; + ImmutableViewportMetrics metrics; ImmutableViewportMetrics oldMetrics = getViewportMetrics(); switch (type) { default: case UPDATE: - metrics = new ViewportMetrics(messageMetrics); // Keep the old viewport size - metrics.setSize(oldMetrics.getSize()); + metrics = messageMetrics.setViewportSize(oldMetrics.getSize()); abortPanZoomAnimation(); break; case PAGE_SIZE: @@ -324,12 +319,11 @@ public class GeckoLayerClient // between the rendered content (which is what Gecko tells us) // and our zoom level (which may have diverged). float scaleFactor = oldMetrics.zoomFactor / messageMetrics.zoomFactor; - metrics = new ViewportMetrics(oldMetrics); - metrics.setPageRect(RectUtils.scale(messageMetrics.getPageRect(), scaleFactor), messageMetrics.getCssPageRect()); + metrics = oldMetrics.setPageRect(RectUtils.scale(messageMetrics.getPageRect(), scaleFactor), messageMetrics.getCssPageRect()); break; } - final ImmutableViewportMetrics newMetrics = new ImmutableViewportMetrics(metrics); + final ImmutableViewportMetrics newMetrics = metrics; post(new Runnable() { public void run() { mGeckoViewport = newMetrics; @@ -467,12 +461,11 @@ public class GeckoLayerClient float pageLeft, float pageTop, float pageRight, float pageBottom, float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) { synchronized (this) { - ViewportMetrics currentMetrics = new ViewportMetrics(getViewportMetrics()); - currentMetrics.setOrigin(new PointF(offsetX, offsetY)); - currentMetrics.setZoomFactor(zoom); - currentMetrics.setPageRect(new RectF(pageLeft, pageTop, pageRight, pageBottom), - new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom)); - final ImmutableViewportMetrics newMetrics = new ImmutableViewportMetrics(currentMetrics); + final ImmutableViewportMetrics newMetrics = getViewportMetrics() + .setViewportOrigin(offsetX, offsetY) + .setZoomFactor(zoom) + .setPageRect(new RectF(pageLeft, pageTop, pageRight, pageBottom), + new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom)); // Since we have switched to displaying a different document, we need to update any // viewport-related state we have lying around. This includes mGeckoViewport and // mViewportMetrics. Usually this information is updated via handleViewportMessage diff --git a/mobile/android/base/gfx/ImmutableViewportMetrics.java b/mobile/android/base/gfx/ImmutableViewportMetrics.java index e1facd13231..c66bc12af3a 100644 --- a/mobile/android/base/gfx/ImmutableViewportMetrics.java +++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java @@ -9,6 +9,7 @@ import org.mozilla.gecko.util.FloatUtils; import android.graphics.PointF; import android.graphics.RectF; +import android.util.DisplayMetrics; /** * ImmutableViewportMetrics are used to store the viewport metrics @@ -33,26 +34,12 @@ public class ImmutableViewportMetrics { public final float viewportRectBottom; public final float zoomFactor; - public ImmutableViewportMetrics(ViewportMetrics m) { - RectF viewportRect = m.getViewport(); - viewportRectLeft = viewportRect.left; - viewportRectTop = viewportRect.top; - viewportRectRight = viewportRect.right; - viewportRectBottom = viewportRect.bottom; - - RectF pageRect = m.getPageRect(); - pageRectLeft = pageRect.left; - pageRectTop = pageRect.top; - pageRectRight = pageRect.right; - pageRectBottom = pageRect.bottom; - - RectF cssPageRect = m.getCssPageRect(); - cssPageRectLeft = cssPageRect.left; - cssPageRectTop = cssPageRect.top; - cssPageRectRight = cssPageRect.right; - cssPageRectBottom = cssPageRect.bottom; - - zoomFactor = m.getZoomFactor(); + public ImmutableViewportMetrics(DisplayMetrics metrics) { + viewportRectLeft = pageRectLeft = cssPageRectLeft = 0; + viewportRectTop = pageRectTop = cssPageRectTop = 0; + viewportRectRight = pageRectRight = cssPageRectRight = metrics.widthPixels; + viewportRectBottom = pageRectBottom = cssPageRectBottom = metrics.heightPixels; + zoomFactor = 1.0f; } private ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop, @@ -84,8 +71,6 @@ public class ImmutableViewportMetrics { return viewportRectBottom - viewportRectTop; } - // some helpers to make ImmutableViewportMetrics act more like ViewportMetrics - public PointF getOrigin() { return new PointF(viewportRectLeft, viewportRectTop); } @@ -143,6 +128,14 @@ public class ImmutableViewportMetrics { FloatUtils.interpolate(zoomFactor, to.zoomFactor, t)); } + public ImmutableViewportMetrics setViewportSize(FloatSize size) { + return new ImmutableViewportMetrics( + pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, + viewportRectLeft, viewportRectTop, viewportRectLeft + size.width, viewportRectTop + size.height, + zoomFactor); + } + public ImmutableViewportMetrics setViewportOrigin(float newOriginX, float newOriginY) { return new ImmutableViewportMetrics( pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, @@ -151,10 +144,26 @@ public class ImmutableViewportMetrics { zoomFactor); } + public ImmutableViewportMetrics setZoomFactor(float newZoomFactor) { + return new ImmutableViewportMetrics( + pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, + viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom, + newZoomFactor); + } + public ImmutableViewportMetrics offsetViewportBy(PointF delta) { return setViewportOrigin(viewportRectLeft + delta.x, viewportRectTop + delta.y); } + public ImmutableViewportMetrics setPageRect(RectF pageRect, RectF cssPageRect) { + return new ImmutableViewportMetrics( + pageRect.left, pageRect.top, pageRect.right, pageRect.bottom, + cssPageRect.left, cssPageRect.top, cssPageRect.right, cssPageRect.bottom, + viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom, + zoomFactor); + } + /* This will set the zoom factor and re-scale page-size and viewport offset * accordingly. The given focus will remain at the same point on the screen * after scaling. diff --git a/mobile/android/base/gfx/ViewportMetrics.java b/mobile/android/base/gfx/ViewportMetrics.java deleted file mode 100644 index 30f7ebf3663..00000000000 --- a/mobile/android/base/gfx/ViewportMetrics.java +++ /dev/null @@ -1,175 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.gfx; - -import org.mozilla.gecko.util.FloatUtils; - -import org.json.JSONException; -import org.json.JSONObject; - -import android.graphics.PointF; -import android.graphics.RectF; -import android.util.DisplayMetrics; - -/** - * ViewportMetrics manages state and contains some utility functions related to - * the page viewport for the Gecko layer client to use. - */ -public class ViewportMetrics { - private static final String LOGTAG = "GeckoViewportMetrics"; - - private RectF mPageRect; - private RectF mCssPageRect; - private RectF mViewportRect; - private float mZoomFactor; - - public ViewportMetrics(DisplayMetrics metrics) { - mPageRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels); - mCssPageRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels); - mViewportRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels); - mZoomFactor = 1.0f; - } - - public ViewportMetrics(ViewportMetrics viewport) { - mPageRect = new RectF(viewport.getPageRect()); - mCssPageRect = new RectF(viewport.getCssPageRect()); - mViewportRect = new RectF(viewport.getViewport()); - mZoomFactor = viewport.getZoomFactor(); - } - - public ViewportMetrics(ImmutableViewportMetrics viewport) { - mPageRect = new RectF(viewport.pageRectLeft, - viewport.pageRectTop, - viewport.pageRectRight, - viewport.pageRectBottom); - mCssPageRect = new RectF(viewport.cssPageRectLeft, - viewport.cssPageRectTop, - viewport.cssPageRectRight, - viewport.cssPageRectBottom); - mViewportRect = new RectF(viewport.viewportRectLeft, - viewport.viewportRectTop, - viewport.viewportRectRight, - viewport.viewportRectBottom); - mZoomFactor = viewport.zoomFactor; - } - - public ViewportMetrics(JSONObject json) throws JSONException { - float x = (float)json.getDouble("x"); - float y = (float)json.getDouble("y"); - float width = (float)json.getDouble("width"); - float height = (float)json.getDouble("height"); - float pageLeft = (float)json.getDouble("pageLeft"); - float pageTop = (float)json.getDouble("pageTop"); - float pageRight = (float)json.getDouble("pageRight"); - float pageBottom = (float)json.getDouble("pageBottom"); - float cssPageLeft = (float)json.getDouble("cssPageLeft"); - float cssPageTop = (float)json.getDouble("cssPageTop"); - float cssPageRight = (float)json.getDouble("cssPageRight"); - float cssPageBottom = (float)json.getDouble("cssPageBottom"); - float zoom = (float)json.getDouble("zoom"); - - mPageRect = new RectF(pageLeft, pageTop, pageRight, pageBottom); - mCssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom); - mViewportRect = new RectF(x, y, x + width, y + height); - mZoomFactor = zoom; - } - - public ViewportMetrics(float x, float y, float width, float height, - float pageLeft, float pageTop, float pageRight, float pageBottom, - float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom, - float zoom) { - mPageRect = new RectF(pageLeft, pageTop, pageRight, pageBottom); - mCssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom); - mViewportRect = new RectF(x, y, x + width, y + height); - mZoomFactor = zoom; - } - - public PointF getOrigin() { - return new PointF(mViewportRect.left, mViewportRect.top); - } - - public FloatSize getSize() { - return new FloatSize(mViewportRect.width(), mViewportRect.height()); - } - - public RectF getViewport() { - return mViewportRect; - } - - public RectF getCssViewport() { - return RectUtils.scale(mViewportRect, 1/mZoomFactor); - } - - public RectF getPageRect() { - return mPageRect; - } - - public RectF getCssPageRect() { - return mCssPageRect; - } - - public float getZoomFactor() { - return mZoomFactor; - } - - public void setPageRect(RectF pageRect, RectF cssPageRect) { - mPageRect = pageRect; - mCssPageRect = cssPageRect; - } - - public void setViewport(RectF viewport) { - mViewportRect = viewport; - } - - public void setOrigin(PointF origin) { - mViewportRect.set(origin.x, origin.y, - origin.x + mViewportRect.width(), - origin.y + mViewportRect.height()); - } - - public void setSize(FloatSize size) { - mViewportRect.right = mViewportRect.left + size.width; - mViewportRect.bottom = mViewportRect.top + size.height; - } - - public void setZoomFactor(float zoomFactor) { - mZoomFactor = zoomFactor; - } - - public String toJSON() { - // Round off height and width. Since the height and width are the size of the screen, it - // makes no sense to send non-integer coordinates to Gecko. - int height = Math.round(mViewportRect.height()); - int width = Math.round(mViewportRect.width()); - - StringBuffer sb = new StringBuffer(512); - sb.append("{ \"x\" : ").append(mViewportRect.left) - .append(", \"y\" : ").append(mViewportRect.top) - .append(", \"width\" : ").append(width) - .append(", \"height\" : ").append(height) - .append(", \"pageLeft\" : ").append(mPageRect.left) - .append(", \"pageTop\" : ").append(mPageRect.top) - .append(", \"pageRight\" : ").append(mPageRect.right) - .append(", \"pageBottom\" : ").append(mPageRect.bottom) - .append(", \"cssPageLeft\" : ").append(mCssPageRect.left) - .append(", \"cssPageTop\" : ").append(mCssPageRect.top) - .append(", \"cssPageRight\" : ").append(mCssPageRect.right) - .append(", \"cssPageBottom\" : ").append(mCssPageRect.bottom) - .append(", \"zoom\" : ").append(mZoomFactor) - .append(" }"); - return sb.toString(); - } - - @Override - public String toString() { - StringBuffer buff = new StringBuffer(256); - buff.append("v=").append(mViewportRect.toString()) - .append(" p=").append(mPageRect.toString()) - .append(" c=").append(mCssPageRect.toString()) - .append(" z=").append(mZoomFactor); - return buff.toString(); - } -} From 1659b1784a06e61506b3c598fbb75c2fc89fba57 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 11:47:08 -0500 Subject: [PATCH 41/77] Bug 809199 - Update setViewportSize to take width/height instead of a FloatSize. r=Cwiiis --- mobile/android/base/gfx/GLController.java | 2 +- mobile/android/base/gfx/GeckoLayerClient.java | 8 ++++---- mobile/android/base/gfx/ImmutableViewportMetrics.java | 4 ++-- mobile/android/base/gfx/LayerView.java | 5 ++--- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/mobile/android/base/gfx/GLController.java b/mobile/android/base/gfx/GLController.java index 16363ae40ef..0498e8def9c 100644 --- a/mobile/android/base/gfx/GLController.java +++ b/mobile/android/base/gfx/GLController.java @@ -127,7 +127,7 @@ public class GLController { // done on the main UI thread, not the GL renderer thread mView.post(new Runnable() { public void run() { - mView.setViewportSize(new IntSize(mWidth, mHeight)); + mView.setViewportSize(mWidth, mHeight); } }); } diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index 3ae8eed82fe..0ecf3e66f9d 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -191,8 +191,8 @@ public class GeckoLayerClient * to the layer client. That way, the layer client won't be tempted to call this, which might * result in an infinite loop. */ - void setViewportSize(FloatSize size) { - mViewportMetrics = mViewportMetrics.setViewportSize(size); + void setViewportSize(int width, int height) { + mViewportMetrics = mViewportMetrics.setViewportSize(width, height); if (mGeckoIsReady) { // here we send gecko a resize message. The code in browser.js is responsible for @@ -311,7 +311,7 @@ public class GeckoLayerClient default: case UPDATE: // Keep the old viewport size - metrics = messageMetrics.setViewportSize(oldMetrics.getSize()); + metrics = messageMetrics.setViewportSize(oldMetrics.getWidth(), oldMetrics.getHeight()); abortPanZoomAnimation(); break; case PAGE_SIZE: @@ -629,7 +629,7 @@ public class GeckoLayerClient /** Implementation of LayerView.Listener */ public void surfaceChanged(int width, int height) { - setViewportSize(new FloatSize(width, height)); + setViewportSize(width, height); // We need to make this call even when the compositor isn't currently // paused (e.g. during an orientation change), to make the compositor diff --git a/mobile/android/base/gfx/ImmutableViewportMetrics.java b/mobile/android/base/gfx/ImmutableViewportMetrics.java index c66bc12af3a..6381d1a34d2 100644 --- a/mobile/android/base/gfx/ImmutableViewportMetrics.java +++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java @@ -128,11 +128,11 @@ public class ImmutableViewportMetrics { FloatUtils.interpolate(zoomFactor, to.zoomFactor, t)); } - public ImmutableViewportMetrics setViewportSize(FloatSize size) { + public ImmutableViewportMetrics setViewportSize(float width, float height) { return new ImmutableViewportMetrics( pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, - viewportRectLeft, viewportRectTop, viewportRectLeft + size.width, viewportRectTop + size.height, + viewportRectLeft, viewportRectTop, viewportRectLeft + width, viewportRectTop + height, zoomFactor); } diff --git a/mobile/android/base/gfx/LayerView.java b/mobile/android/base/gfx/LayerView.java index 3c3bae83dd2..d1e1216bf13 100644 --- a/mobile/android/base/gfx/LayerView.java +++ b/mobile/android/base/gfx/LayerView.java @@ -209,9 +209,8 @@ public class LayerView extends FrameLayout { mLayerClient.setZoomConstraints(constraints); } - /** The LayerRenderer calls this to indicate that the window has changed size. */ - public void setViewportSize(IntSize size) { - mLayerClient.setViewportSize(new FloatSize(size)); + public void setViewportSize(int width, int height) { + mLayerClient.setViewportSize(width, height); } public void setInputConnectionHandler(InputConnectionHandler inputConnectionHandler) { From 3c21360542710271c5ed418a509c6c36420544d9 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 7 Nov 2012 11:47:17 -0500 Subject: [PATCH 42/77] Bug 809199 - Update offsetViewportBy to take dx/dy instead of PointF. r=Cwiiis --- mobile/android/base/gfx/ImmutableViewportMetrics.java | 4 ++-- mobile/android/base/ui/PanZoomController.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mobile/android/base/gfx/ImmutableViewportMetrics.java b/mobile/android/base/gfx/ImmutableViewportMetrics.java index 6381d1a34d2..4ca8515a8f4 100644 --- a/mobile/android/base/gfx/ImmutableViewportMetrics.java +++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java @@ -152,8 +152,8 @@ public class ImmutableViewportMetrics { newZoomFactor); } - public ImmutableViewportMetrics offsetViewportBy(PointF delta) { - return setViewportOrigin(viewportRectLeft + delta.x, viewportRectTop + delta.y); + public ImmutableViewportMetrics offsetViewportBy(float dx, float dy) { + return setViewportOrigin(viewportRectLeft + dx, viewportRectTop + dy); } public ImmutableViewportMetrics setPageRect(RectF pageRect, RectF cssPageRect) { diff --git a/mobile/android/base/ui/PanZoomController.java b/mobile/android/base/ui/PanZoomController.java index adbec52b761..2f00a5887fb 100644 --- a/mobile/android/base/ui/PanZoomController.java +++ b/mobile/android/base/ui/PanZoomController.java @@ -480,8 +480,8 @@ public class PanZoomController updatePosition(); } - private void scrollBy(PointF point) { - ImmutableViewportMetrics scrolled = getMetrics().offsetViewportBy(point); + private void scrollBy(float dx, float dy) { + ImmutableViewportMetrics scrolled = getMetrics().offsetViewportBy(dx, dy); mTarget.setViewportMetrics(scrolled); } @@ -575,7 +575,7 @@ public class PanZoomController } if (! mSubscroller.scrollBy(displacement)) { synchronized (mTarget.getLock()) { - scrollBy(displacement); + scrollBy(displacement.x, displacement.y); } } } @@ -886,8 +886,8 @@ public class PanZoomController newZoomFactor = maxZoomFactor + excessZoom; } - scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(), - mLastZoomFocus.y - detector.getFocusY())); + scrollBy(mLastZoomFocus.x - detector.getFocusX(), + mLastZoomFocus.y - detector.getFocusY()); PointF focus = new PointF(detector.getFocusX(), detector.getFocusY()); scaleWithFocus(newZoomFactor, focus); } From 226d823007ea3e53190ba7cba85fa138c9c202c5 Mon Sep 17 00:00:00 2001 From: Mounir Lamouri Date: Wed, 7 Nov 2012 16:51:32 +0000 Subject: [PATCH 43/77] Bug 809425 - Fix GCC 4.7 warnings in nsDocumentViewer.cpp. r=bz --- layout/base/nsDocumentViewer.cpp | 54 +++++++++++++++++--------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 07ae59da2e0..e0e909ab196 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -176,6 +176,7 @@ using namespace mozilla; #ifdef PR_LOGGING +#ifdef NS_PRINTING static PRLogModuleInfo * GetPrintingLog() { @@ -185,6 +186,7 @@ GetPrintingLog() return sLog; } #define PR_PL(_p1) PR_LOG(GetPrintingLog(), PR_LOG_DEBUG, _p1); +#endif // NS_PRINTING #define PRT_YESNO(_p) ((_p)?"YES":"NO") #else @@ -4198,7 +4200,32 @@ DocumentViewerImpl::IncrementDestroyRefCount() //------------------------------------------------------------ -static void ResetFocusState(nsIDocShell* aDocShell); +#if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW) +//------------------------------------------------------------ +// Reset ESM focus for all descendent doc shells. +static void +ResetFocusState(nsIDocShell* aDocShell) +{ + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) + return; + + nsCOMPtr docShellEnumerator; + aDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, + nsIDocShell::ENUMERATE_FORWARDS, + getter_AddRefs(docShellEnumerator)); + + nsCOMPtr currentContainer; + bool hasMoreDocShells; + while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) + && hasMoreDocShells) { + docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); + nsCOMPtr win = do_GetInterface(currentContainer); + if (win) + fm->ClearFocus(win); + } +} +#endif // NS_PRINTING && NS_PRINT_PREVIEW void DocumentViewerImpl::ReturnToGalleyPresentation() @@ -4226,31 +4253,6 @@ DocumentViewerImpl::ReturnToGalleyPresentation() #endif // NS_PRINTING && NS_PRINT_PREVIEW } -//------------------------------------------------------------ -// Reset ESM focus for all descendent doc shells. -static void -ResetFocusState(nsIDocShell* aDocShell) -{ - nsIFocusManager* fm = nsFocusManager::GetFocusManager(); - if (!fm) - return; - - nsCOMPtr docShellEnumerator; - aDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, - nsIDocShell::ENUMERATE_FORWARDS, - getter_AddRefs(docShellEnumerator)); - - nsCOMPtr currentContainer; - bool hasMoreDocShells; - while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) - && hasMoreDocShells) { - docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); - nsCOMPtr win = do_GetInterface(currentContainer); - if (win) - fm->ClearFocus(win); - } -} - //------------------------------------------------------------ // This called ONLY when printing has completed and the DV // is being notified that it should get rid of the PrintEngine. From 95d45d92eeb009ac6c55feaed74c751eaf5c12c8 Mon Sep 17 00:00:00 2001 From: Robert Longson Date: Wed, 7 Nov 2012 09:53:44 +0000 Subject: [PATCH 44/77] Bug 779971 - Make nsSVGTextPathProperty::DoUpdate trigger nsSVGTextFrame::NotifyGlyphMetricsChange() off an asynchronous change hint (to avoid calling nsLayoutUtils::FrameNeedsReflow synchronously under nsISVGChildFrame::ReflowSVG or during frame teardown, and avoid infinite loops caused by using an event queue event). r=jwatt. --HG-- extra : rebase_source : 4dc4e59cf423f6ffb02826fb2f357edda85c048c --- layout/base/nsCSSFrameConstructor.cpp | 7 +++++ layout/base/nsChangeHint.h | 8 +++++- layout/svg/crashtests/779971-1.svg | 14 ++++++++++ layout/svg/crashtests/crashtests.list | 1 + layout/svg/nsSVGEffects.cpp | 38 ++++----------------------- 5 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 layout/svg/crashtests/779971-1.svg diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 2c191a0383f..ecd036628e3 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -116,6 +116,7 @@ #include "nsIDOMSVGFilters.h" #include "DOMSVGTests.h" #include "nsSVGEffects.h" +#include "nsSVGTextPathFrame.h" #include "nsSVGUtils.h" #include "nsRefreshDriver.h" @@ -7797,6 +7798,12 @@ DoApplyRenderingChangeToTree(nsIFrame* aFrame, aFrame->InvalidateFrameSubtree(); } } + if (aChange & nsChangeHint_UpdateTextPath) { + NS_ABORT_IF_FALSE(aFrame->GetType() == nsGkAtoms::svgTextPathFrame, + "textPath frame expected"); + // Invalidate and reflow the entire nsSVGTextFrame: + static_cast(aFrame)->NotifyGlyphMetricsChange(); + } if (aChange & nsChangeHint_UpdateOpacityLayer) { // FIXME/bug 796697: we can get away with empty transactions for // opacity updates in many cases. diff --git a/layout/base/nsChangeHint.h b/layout/base/nsChangeHint.h index ff3b0253acd..e2a1e9f62c7 100644 --- a/layout/base/nsChangeHint.h +++ b/layout/base/nsChangeHint.h @@ -110,7 +110,13 @@ enum nsChangeHint { * changes, and it's inherited by a child, that might require a reflow * due to the border-width change on the child. */ - nsChangeHint_BorderStyleNoneChange = 0x8000 + nsChangeHint_BorderStyleNoneChange = 0x8000, + + /** + * SVG textPath needs to be recomputed because the path has changed. + * This means that the glyph positions of the text need to be recomputed. + */ + nsChangeHint_UpdateTextPath = 0x10000 // IMPORTANT NOTE: When adding new hints, consider whether you need to // add them to NS_HintsNotHandledForDescendantsIn() below. diff --git a/layout/svg/crashtests/779971-1.svg b/layout/svg/crashtests/779971-1.svg new file mode 100644 index 00000000000..d57065a0ba8 --- /dev/null +++ b/layout/svg/crashtests/779971-1.svg @@ -0,0 +1,14 @@ + +x1 + + diff --git a/layout/svg/crashtests/crashtests.list b/layout/svg/crashtests/crashtests.list index d01bb92fee5..e026a384623 100644 --- a/layout/svg/crashtests/crashtests.list +++ b/layout/svg/crashtests/crashtests.list @@ -136,6 +136,7 @@ load 767056-1.svg load 768351.svg load 780963-1.html load 757751-1.svg +load 779971-1.svg load 782141-1.svg load 784061-1.svg load 790072.svg diff --git a/layout/svg/nsSVGEffects.cpp b/layout/svg/nsSVGEffects.cpp index f6830b264d6..8424df4ca8a 100644 --- a/layout/svg/nsSVGEffects.cpp +++ b/layout/svg/nsSVGEffects.cpp @@ -286,32 +286,6 @@ nsSVGMarkerProperty::DoUpdate() mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); } -class nsAsyncNotifyGlyphMetricsChange MOZ_FINAL : public nsIReflowCallback -{ -public: - nsAsyncNotifyGlyphMetricsChange(nsIFrame* aFrame) : mWeakFrame(aFrame) - { - } - - virtual bool ReflowFinished() - { - nsSVGTextPathFrame* frame = - static_cast(mWeakFrame.GetFrame()); - if (frame) { - frame->NotifyGlyphMetricsChange(); - } - delete this; - return true; - } - - virtual void ReflowCallbackCanceled() - { - delete this; - } - - nsWeakFrame mWeakFrame; -}; - void nsSVGTextPathProperty::DoUpdate() { @@ -322,13 +296,11 @@ nsSVGTextPathProperty::DoUpdate() NS_ASSERTION(mFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); if (mFrame->GetType() == nsGkAtoms::svgTextPathFrame) { - if (mFrame->PresContext()->PresShell()->IsReflowLocked()) { - nsIReflowCallback* cb = new nsAsyncNotifyGlyphMetricsChange(mFrame); - mFrame->PresContext()->PresShell()->PostReflowCallback(cb); - } else { - nsSVGTextPathFrame* textPathFrame = static_cast(mFrame); - textPathFrame->NotifyGlyphMetricsChange(); - } + // Repaint asynchronously in case the path frame is being torn down + nsChangeHint changeHint = + nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_UpdateTextPath); + mFramePresShell->FrameConstructor()->PostRestyleEvent( + mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); } } From 5ede664aacf75af8e484779971ba7a6e9716d1c9 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Wed, 7 Nov 2012 09:39:32 -0800 Subject: [PATCH 45/77] Bug 809318 - Fix flakey tests. r=philor --- js/xpconnect/tests/mochitest/test_bug781476.html | 2 +- js/xpconnect/tests/mochitest/test_sameOriginPolicy.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/xpconnect/tests/mochitest/test_bug781476.html b/js/xpconnect/tests/mochitest/test_bug781476.html index 2ee52fc38a6..f01cedfd4f5 100644 --- a/js/xpconnect/tests/mochitest/test_bug781476.html +++ b/js/xpconnect/tests/mochitest/test_bug781476.html @@ -13,7 +13,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=781476 Mozilla Bug 781476

     
     
    + diff --git a/js/xpconnect/tests/mochitest/test_sameOriginPolicy.html b/js/xpconnect/tests/mochitest/test_sameOriginPolicy.html index 86ca4bee21e..0fb10512a3a 100644 --- a/js/xpconnect/tests/mochitest/test_sameOriginPolicy.html +++ b/js/xpconnect/tests/mochitest/test_sameOriginPolicy.html @@ -14,7 +14,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=801576

    -
     
     
    + From 95c0ae32b945e41c84a8df39df1560f65c9bcb73 Mon Sep 17 00:00:00 2001 From: Wes Johnston Date: Wed, 7 Nov 2012 09:39:55 -0800 Subject: [PATCH 46/77] Bug 808212 - Expire thumbnails that we don't need. r=rnewman --- .../android/base/db/BrowserProvider.java.in | 34 +++++++++++--- .../base/tests/testBrowserProvider.java.in | 46 +++++++++++++++++-- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/mobile/android/base/db/BrowserProvider.java.in b/mobile/android/base/db/BrowserProvider.java.in index 674f4377a3d..c704b261df1 100644 --- a/mobile/android/base/db/BrowserProvider.java.in +++ b/mobile/android/base/db/BrowserProvider.java.in @@ -89,6 +89,8 @@ public class BrowserProvider extends ContentProvider { // Minimum duration to keep when expiring. static final long DEFAULT_EXPIRY_PRESERVE_WINDOW = 1000L * 60L * 60L * 24L * 28L; // Four weeks. + // Minimum number of thumbnails to keep around. + static final int DEFAULT_EXPIRY_THUMBNAIL_COUNT = 15; static final String TABLE_BOOKMARKS = "bookmarks"; static final String TABLE_HISTORY = "history"; @@ -1427,22 +1429,21 @@ public class BrowserProvider extends ContentProvider { */ public void expireHistory(final SQLiteDatabase db, final int retain, final long keepAfter) { final long rows = DatabaseUtils.queryNumEntries(db, TABLE_HISTORY); + if (retain >= rows) { debug("Not expiring history: only have " + rows + " rows."); return; } + final String sortOrder = BrowserContract.getFrecencySortOrder(false, true); final long toRemove = rows - retain; debug("Expiring at most " + toRemove + " rows earlier than " + keepAfter + "."); - final String sortOrder = BrowserContract.getFrecencySortOrder(false, true); - final String sql; if (keepAfter > 0) { - // If we don't bind these paramaters dynamically, the WHERE clause here can return null - sql = "DELETE FROM " + TABLE_HISTORY + " " + + sql = "DELETE FROM " + TABLE_HISTORY + " " + "WHERE MAX(" + History.DATE_LAST_VISITED + ", " + History.DATE_MODIFIED +") < " + keepAfter + " " + - " AND " + History._ID + " " + "IN ( SELECT " + + " AND " + History._ID + " IN ( SELECT " + History._ID + " FROM " + TABLE_HISTORY + " " + "ORDER BY " + sortOrder + " LIMIT " + toRemove + ")"; @@ -1451,11 +1452,30 @@ public class BrowserProvider extends ContentProvider { "IN ( SELECT " + History._ID + " FROM " + TABLE_HISTORY + " " + "ORDER BY " + sortOrder + " LIMIT " + toRemove + ")"; } - trace("Deleting using query: " + sql); db.execSQL(sql); } + /** + * Remove any thumbnails that for sites that aren't likely to be ever shown. + * Items will be removed according to a frecency calculation. + * + * Call this method within a transaction. + */ + public void expireThumbnails(final SQLiteDatabase db) { + final String sortOrder = BrowserContract.getFrecencySortOrder(true, false); + final String sql = "UPDATE " + TABLE_IMAGES + + " SET " + Images.THUMBNAIL + " = NULL " + + " WHERE " + Images.URL + " NOT IN ( " + + " SELECT " + Combined.URL + + " FROM " + VIEW_COMBINED_WITH_IMAGES + + " ORDER BY " + sortOrder + + " LIMIT " + DEFAULT_EXPIRY_THUMBNAIL_COUNT + + ")"; + trace("Clear thumbs using query: " + sql); + db.execSQL(sql); + } + private boolean isCallerSync(Uri uri) { String isSync = uri.getQueryParameter(BrowserContract.PARAM_IS_SYNC); return !TextUtils.isEmpty(isSync); @@ -1606,6 +1626,8 @@ public class BrowserProvider extends ContentProvider { retainCount = AGGRESSIVE_EXPIRY_RETAIN_COUNT; } expireHistory(db, retainCount, keepAfter); + expireThumbnails(db); + deleteUnusedImages(uri); break; } diff --git a/mobile/android/base/tests/testBrowserProvider.java.in b/mobile/android/base/tests/testBrowserProvider.java.in index 29c8d628464..b22eb15b8c5 100644 --- a/mobile/android/base/tests/testBrowserProvider.java.in +++ b/mobile/android/base/tests/testBrowserProvider.java.in @@ -1593,7 +1593,7 @@ public class testBrowserProvider extends ContentProviderTest { class TestExpireHistory extends Test { private void createFakeHistory(long timeShift, int count) { // Insert a bunch of very new entries - ContentValues allVals[] = new ContentValues[count]; + ContentValues[] allVals = new ContentValues[count]; long time = System.currentTimeMillis() - timeShift; for (int i = 0; i < count; i++) { allVals[i] = new ContentValues(); @@ -1604,7 +1604,7 @@ public class testBrowserProvider extends ContentProviderTest { } int inserts = mProvider.bulkInsert(mHistoryUri, allVals); - mAsserter.is(inserts, count, "Excepted number of inserts matches"); + mAsserter.is(inserts, count, "Expected number of inserts matches"); // inserting a new entry sets the date created and modified automatically // reset all of them @@ -1618,10 +1618,26 @@ public class testBrowserProvider extends ContentProviderTest { Cursor c = mProvider.query(mHistoryUri, null, "", null, null); mAsserter.is(c.getCount(), count, count + " history entries found"); + + // add thumbnails for each entry + allVals = new ContentValues[count]; + for (int i = 0; i < count; i++) { + allVals[i] = new ContentValues(); + allVals[i].put(mImagesThumbnailCol, i); + allVals[i].put(mImagesUrlCol, "http://www.test.org/" + i); + allVals[i].put(mImagesFaviconCol, i); + } + + inserts = mProvider.bulkInsert(mImagesUri, allVals); + mAsserter.is(inserts, count, "Expected number of inserts matches"); + + c = mProvider.query(mImagesUri, null, mImagesThumbnailCol + " IS NOT NULL", null, null); + mAsserter.is(c.getCount(), count, count + " thumbnails entries found"); } public void test() throws Exception { final int count = 3000; + final int thumbCount = 15; // insert a bunch of new entries createFakeHistory(0, count); @@ -1632,14 +1648,25 @@ public class testBrowserProvider extends ContentProviderTest { Cursor c = mProvider.query(mHistoryUri, null, "", null, null); mAsserter.is(c.getCount(), count, count + " history entries found"); + // expiring with a normal priority should delete all but 10 thumbnails + c = mProvider.query(mImagesUri, null, mImagesThumbnailCol + " IS NOT NULL", null, null); + mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found"); + + ensureEmptyDatabase(); + // insert a bunch of new entries + createFakeHistory(0, count); + // expiring with a aggressive priority should leave 500 entries url = appendUriParam(mHistoryOldUri, "PARAM_EXPIRE_PRIORITY", "AGGRESSIVE"); mProvider.delete(url, null, null); c = mProvider.query(mHistoryUri, null, "", null, null); mAsserter.is(c.getCount(), 500, "500 history entries found"); - ensureEmptyDatabase(); + // expiring with a aggressive priority should delete all but 10 thumbnails + c = mProvider.query(mImagesUri, null, mImagesThumbnailCol + " IS NOT NULL", null, null); + mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found"); + ensureEmptyDatabase(); // insert a bunch of entries with an old time created/modified long time = 1000L * 60L * 60L * 24L * 30L * 3L; createFakeHistory(time, count); @@ -1651,12 +1678,25 @@ public class testBrowserProvider extends ContentProviderTest { c = mProvider.query(mHistoryUri, null, "", null, null); mAsserter.is(c.getCount(), 2000, "2000 history entries found"); + // expiring with a normal priority should delete all but 10 thumbnails + c = mProvider.query(mImagesUri, null, mImagesThumbnailCol + " IS NOT NULL", null, null); + mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found"); + + ensureEmptyDatabase(); + // insert a bunch of entries with an old time created/modified + time = 1000L * 60L * 60L * 24L * 30L * 3L; + createFakeHistory(time, count); + // expiring with an agressive priority should remove old // entries leaving at least 500 url = appendUriParam(mHistoryOldUri, "PARAM_EXPIRE_PRIORITY", "AGGRESSIVE"); mProvider.delete(url, null, null); c = mProvider.query(mHistoryUri, null, "", null, null); mAsserter.is(c.getCount(), 500, "500 history entries found"); + + // expiring with a aggressive priority should delete all but 10 thumbnails + c = mProvider.query(mImagesUri, null, mImagesThumbnailCol + " IS NOT NULL", null, null); + mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found"); } } } From 0111ef27598123de3b00b4105be2c2b8d0a0925a Mon Sep 17 00:00:00 2001 From: Jose Antonio Olivera Ortega Date: Wed, 7 Nov 2012 18:51:40 +0100 Subject: [PATCH 47/77] Bug 808994 - B2G SMS: Expose the strict7BitEncoding setting through the setting API. r=vicamo --- b2g/chrome/content/settings.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/b2g/chrome/content/settings.js b/b2g/chrome/content/settings.js index 9c730ef2274..41a4ea44b18 100644 --- a/b2g/chrome/content/settings.js +++ b/b2g/chrome/content/settings.js @@ -131,6 +131,11 @@ SettingsListener.observe('language.current', 'en-US', function(value) { } }); }); + + SettingsListener.observe('ril.sms.strict7BitEncoding.enabled', false, + function(value) { + Services.prefs.setBoolPref('dom.sms.strict7BitEncoding', value); + }); })(); //=================== DeviceInfo ==================== From fc947af2235d99aa05171fed2a441d124a1e32af Mon Sep 17 00:00:00 2001 From: Kannan Vijayan Date: Wed, 7 Nov 2012 13:03:31 -0500 Subject: [PATCH 48/77] Bug 803730 - Add mochitest for bug. --- js/xpconnect/tests/mochitest/Makefile.in | 1 + .../tests/mochitest/test_bug803730.html | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 js/xpconnect/tests/mochitest/test_bug803730.html diff --git a/js/xpconnect/tests/mochitest/Makefile.in b/js/xpconnect/tests/mochitest/Makefile.in index f6160b72b47..52fb41db924 100644 --- a/js/xpconnect/tests/mochitest/Makefile.in +++ b/js/xpconnect/tests/mochitest/Makefile.in @@ -85,6 +85,7 @@ MOCHITEST_FILES = chrome_wrappers_helper.html \ file_bug795275.xml \ file_bug799348.html \ test_bug800864.html \ + test_bug803730.html \ $(NULL) ifneq ($(OS_TARGET),Android) diff --git a/js/xpconnect/tests/mochitest/test_bug803730.html b/js/xpconnect/tests/mochitest/test_bug803730.html new file mode 100644 index 00000000000..4e38b00be78 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug803730.html @@ -0,0 +1,36 @@ + + + + + + Test for Bug 803730 + + + + +Mozilla Bug 803730 +

    + +
    +
    +
    + + From f5b4cb0cee850a4ebf0084a8da4c5387d0120ca6 Mon Sep 17 00:00:00 2001 From: Kannan Vijayan Date: Wed, 7 Nov 2012 13:04:01 -0500 Subject: [PATCH 49/77] Bug 803730 - Fix ion to box LHS operand when performing InstanceOf on an object. (r=sstangl) --- js/src/ion/CodeGenerator.cpp | 43 +++++++++++++++++++++++---- js/src/ion/arm/CodeGenerator-arm.cpp | 8 +++++ js/src/ion/arm/CodeGenerator-arm.h | 1 + js/src/ion/arm/MacroAssembler-arm.cpp | 6 ++++ js/src/ion/arm/MacroAssembler-arm.h | 1 + js/src/ion/x64/CodeGenerator-x64.cpp | 6 ++++ js/src/ion/x64/CodeGenerator-x64.h | 1 + js/src/ion/x64/MacroAssembler-x64.h | 4 +++ js/src/ion/x86/CodeGenerator-x86.cpp | 8 +++++ js/src/ion/x86/CodeGenerator-x86.h | 1 + js/src/ion/x86/MacroAssembler-x86.h | 5 ++++ 11 files changed, 78 insertions(+), 6 deletions(-) diff --git a/js/src/ion/CodeGenerator.cpp b/js/src/ion/CodeGenerator.cpp index 14d9ef17ef1..5e5c90c80e0 100644 --- a/js/src/ion/CodeGenerator.cpp +++ b/js/src/ion/CodeGenerator.cpp @@ -4027,17 +4027,27 @@ CodeGenerator::emitInstanceOf(LInstruction *ins, Register rhs) Register rhsFlags = ToRegister(ins->getTemp(0)); Register lhsTmp = ToRegister(ins->getTemp(0)); - Label callHasInstance; Label boundFunctionCheck; Label boundFunctionDone; Label done; Label loopPrototypeChain; + JS_ASSERT(ins->isInstanceOfO() || ins->isInstanceOfV()); + bool lhsIsValue = ins->isInstanceOfV(); + typedef bool (*pf)(JSContext *, HandleObject, HandleValue, JSBool *); static const VMFunction HasInstanceInfo = FunctionInfo(js::HasInstance); - OutOfLineCode *call = oolCallVM(HasInstanceInfo, ins, (ArgList(), rhs, ToValue(ins, 0)), - StoreRegisterTo(output)); + // If the lhs is an object, then the ValueOperand that gets sent to + // HasInstance must be boxed first. If the lhs is a value, it can + // be sent directly. Hence the choice between ToValue and ToTempValue + // below. Note that the same check is done below in the generated code + // and explicit boxing instructions emitted before calling the OOL code + // if we're handling a LInstanceOfO. + + OutOfLineCode *call = oolCallVM(HasInstanceInfo, ins, + (ArgList(), rhs, lhsIsValue ? ToValue(ins, 0) : ToTempValue(ins, 0)), + StoreRegisterTo(output)); if (!call) return false; @@ -4059,7 +4069,18 @@ CodeGenerator::emitInstanceOf(LInstruction *ins, Register rhs) masm.loadBaseShape(rhsTmp, output); masm.cmpPtr(Address(output, BaseShape::offsetOfClass()), ImmWord(&js::FunctionClass)); - masm.j(Assembler::NotEqual, call->entry()); + if (lhsIsValue) { + // If the input LHS is a value, no boxing necessary. + masm.j(Assembler::NotEqual, call->entry()); + } else { + // If the input LHS is raw object pointer, it must be boxed before + // calling into js::HasInstance. + Label dontCallHasInstance; + masm.j(Assembler::Equal, &dontCallHasInstance); + masm.boxNonDouble(JSVAL_TYPE_OBJECT, ToRegister(ins->getOperand(0)), ToTempValue(ins, 0)); + masm.jump(call->entry()); + masm.bind(&dontCallHasInstance); + } // Check Bound Function masm.loadPtr(Address(output, BaseShape::offsetOfFlags()), rhsFlags); @@ -4093,7 +4114,7 @@ CodeGenerator::emitInstanceOf(LInstruction *ins, Register rhs) // When lhs is a value: The HasInstance for function objects always // return false when lhs isn't an object. So check if // lhs is an object and otherwise return false - if (ins->isInstanceOfV()) { + if (lhsIsValue) { Label isObject; ValueOperand lhsValue = ToValue(ins, LInstanceOfV::LHS); masm.branchTestObject(Assembler::Equal, lhsValue, &isObject); @@ -4132,7 +4153,17 @@ CodeGenerator::emitInstanceOf(LInstruction *ins, Register rhs) masm.loadPtr(Address(lhsTmp, offsetof(types::TypeObject, proto)), lhsTmp); // Bail out if we hit a lazy proto - masm.branch32(Assembler::Equal, lhsTmp, Imm32(1), call->entry()); + if (lhsIsValue) { + masm.branch32(Assembler::Equal, lhsTmp, Imm32(1), call->entry()); + } else { + // If the input LHS is raw object pointer, it must be boxed before + // calling into js::HasInstance. + Label dontCallHasInstance; + masm.branch32(Assembler::NotEqual, lhsTmp, Imm32(1), &dontCallHasInstance); + masm.boxNonDouble(JSVAL_TYPE_OBJECT, ToRegister(ins->getOperand(0)), ToTempValue(ins, 0)); + masm.jump(call->entry()); + masm.bind(&dontCallHasInstance); + } masm.testPtr(lhsTmp, lhsTmp); masm.j(Assembler::Zero, &done); diff --git a/js/src/ion/arm/CodeGenerator-arm.cpp b/js/src/ion/arm/CodeGenerator-arm.cpp index 0dbce9f5326..97cd7d6321f 100644 --- a/js/src/ion/arm/CodeGenerator-arm.cpp +++ b/js/src/ion/arm/CodeGenerator-arm.cpp @@ -1029,6 +1029,14 @@ CodeGeneratorARM::ToOutValue(LInstruction *ins) return ValueOperand(typeReg, payloadReg); } +ValueOperand +CodeGeneratorARM::ToTempValue(LInstruction *ins, size_t pos) +{ + Register typeReg = ToRegister(ins->getTemp(pos + TYPE_INDEX)); + Register payloadReg = ToRegister(ins->getTemp(pos + PAYLOAD_INDEX)); + return ValueOperand(typeReg, payloadReg); +} + bool CodeGeneratorARM::visitValue(LValue *value) { diff --git a/js/src/ion/arm/CodeGenerator-arm.h b/js/src/ion/arm/CodeGenerator-arm.h index 94c9772ef33..779b46acf54 100644 --- a/js/src/ion/arm/CodeGenerator-arm.h +++ b/js/src/ion/arm/CodeGenerator-arm.h @@ -110,6 +110,7 @@ class CodeGeneratorARM : public CodeGeneratorShared protected: ValueOperand ToValue(LInstruction *ins, size_t pos); ValueOperand ToOutValue(LInstruction *ins); + ValueOperand ToTempValue(LInstruction *ins, size_t pos); // Functions for LTestVAndBranch. Register splitTagForTest(const ValueOperand &value); diff --git a/js/src/ion/arm/MacroAssembler-arm.cpp b/js/src/ion/arm/MacroAssembler-arm.cpp index 2d1c0aa9f51..e372a2233aa 100644 --- a/js/src/ion/arm/MacroAssembler-arm.cpp +++ b/js/src/ion/arm/MacroAssembler-arm.cpp @@ -2235,6 +2235,12 @@ MacroAssemblerARMCompat::boxDouble(const FloatRegister &src, const ValueOperand as_vxfer(dest.payloadReg(), dest.typeReg(), VFPRegister(src), FloatToCore); } +void +MacroAssemblerARMCompat::boxNonDouble(JSValueType type, const Register &src, const ValueOperand &dest) { + if (src != dest.payloadReg()) + ma_mov(src, dest.payloadReg()); + ma_mov(Imm32(type), dest.typeReg()); +} void MacroAssemblerARMCompat::boolValueToDouble(const ValueOperand &operand, const FloatRegister &dest) diff --git a/js/src/ion/arm/MacroAssembler-arm.h b/js/src/ion/arm/MacroAssembler-arm.h index 05fcb951395..95ead8c5150 100644 --- a/js/src/ion/arm/MacroAssembler-arm.h +++ b/js/src/ion/arm/MacroAssembler-arm.h @@ -549,6 +549,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM // boxing code void boxDouble(const FloatRegister &src, const ValueOperand &dest); + void boxNonDouble(JSValueType type, const Register &src, const ValueOperand &dest); // Extended unboxing API. If the payload is already in a register, returns // that register. Otherwise, provides a move to the given scratch register, diff --git a/js/src/ion/x64/CodeGenerator-x64.cpp b/js/src/ion/x64/CodeGenerator-x64.cpp index 6d0edf53686..4f3b0a7dfe2 100644 --- a/js/src/ion/x64/CodeGenerator-x64.cpp +++ b/js/src/ion/x64/CodeGenerator-x64.cpp @@ -33,6 +33,12 @@ CodeGeneratorX64::ToOutValue(LInstruction *ins) return ValueOperand(ToRegister(ins->getDef(0))); } +ValueOperand +CodeGeneratorX64::ToTempValue(LInstruction *ins, size_t pos) +{ + return ValueOperand(ToRegister(ins->getTemp(pos))); +} + bool CodeGeneratorX64::visitDouble(LDouble *ins) { diff --git a/js/src/ion/x64/CodeGenerator-x64.h b/js/src/ion/x64/CodeGenerator-x64.h index d08e34968f2..dfd368f9956 100644 --- a/js/src/ion/x64/CodeGenerator-x64.h +++ b/js/src/ion/x64/CodeGenerator-x64.h @@ -23,6 +23,7 @@ class CodeGeneratorX64 : public CodeGeneratorX86Shared protected: ValueOperand ToValue(LInstruction *ins, size_t pos); ValueOperand ToOutValue(LInstruction *ins); + ValueOperand ToTempValue(LInstruction *ins, size_t pos); void loadUnboxedValue(Operand source, MIRType type, const LDefinition *dest); diff --git a/js/src/ion/x64/MacroAssembler-x64.h b/js/src/ion/x64/MacroAssembler-x64.h index 368d158d49a..572ab1c2144 100644 --- a/js/src/ion/x64/MacroAssembler-x64.h +++ b/js/src/ion/x64/MacroAssembler-x64.h @@ -647,6 +647,10 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared void boxDouble(const FloatRegister &src, const ValueOperand &dest) { movqsd(src, dest.valueReg()); } + void boxNonDouble(JSValueType type, const Register &src, const ValueOperand &dest) { + JS_ASSERT(src != dest.valueReg()); + boxValue(type, src, dest.valueReg()); + } // Note that the |dest| register here may be ScratchReg, so we shouldn't // use it. diff --git a/js/src/ion/x86/CodeGenerator-x86.cpp b/js/src/ion/x86/CodeGenerator-x86.cpp index 8f2cc2c5791..149e97a0011 100644 --- a/js/src/ion/x86/CodeGenerator-x86.cpp +++ b/js/src/ion/x86/CodeGenerator-x86.cpp @@ -68,6 +68,14 @@ CodeGeneratorX86::ToOutValue(LInstruction *ins) return ValueOperand(typeReg, payloadReg); } +ValueOperand +CodeGeneratorX86::ToTempValue(LInstruction *ins, size_t pos) +{ + Register typeReg = ToRegister(ins->getTemp(pos + TYPE_INDEX)); + Register payloadReg = ToRegister(ins->getTemp(pos + PAYLOAD_INDEX)); + return ValueOperand(typeReg, payloadReg); +} + bool CodeGeneratorX86::visitValue(LValue *value) { diff --git a/js/src/ion/x86/CodeGenerator-x86.h b/js/src/ion/x86/CodeGenerator-x86.h index 2069ebad4b0..af484fa0029 100644 --- a/js/src/ion/x86/CodeGenerator-x86.h +++ b/js/src/ion/x86/CodeGenerator-x86.h @@ -43,6 +43,7 @@ class CodeGeneratorX86 : public CodeGeneratorX86Shared protected: ValueOperand ToValue(LInstruction *ins, size_t pos); ValueOperand ToOutValue(LInstruction *ins); + ValueOperand ToTempValue(LInstruction *ins, size_t pos); void storeElementTyped(const LAllocation *value, MIRType valueType, MIRType elementType, const Register &elements, const LAllocation *index); diff --git a/js/src/ion/x86/MacroAssembler-x86.h b/js/src/ion/x86/MacroAssembler-x86.h index 8b240622671..90f9838f90e 100644 --- a/js/src/ion/x86/MacroAssembler-x86.h +++ b/js/src/ion/x86/MacroAssembler-x86.h @@ -533,6 +533,11 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared psrldq(Imm32(4), src); movd(src, dest.typeReg()); } + void boxNonDouble(JSValueType type, const Register &src, const ValueOperand &dest) { + if (src != dest.payloadReg()) + movl(src, dest.payloadReg()); + movl(Imm32(type), dest.typeReg()); + } void unboxInt32(const ValueOperand &src, const Register &dest) { movl(src.payloadReg(), dest); } From 06198ad8de9a35462ff4047c6f556eb48161b7aa Mon Sep 17 00:00:00 2001 From: Chris Lord Date: Wed, 7 Nov 2012 18:06:57 +0000 Subject: [PATCH 50/77] Bug 807299 - Fix incorrect progressive update cancelling. r=bgirard Sometimes the rounding error on display ports is over a pixel - in this case, progressive updates may be cancelled incorrectly and cause drawing to be missed until the next invalidation (assuming that isn't also incorrectly cancelled). --- mobile/android/base/gfx/GeckoLayerClient.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index 0ecf3e66f9d..f80336ce1b6 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -376,16 +376,17 @@ public class GeckoLayerClient // XXX All sorts of rounding happens inside Gecko that becomes hard to // account exactly for. Given we align the display-port to tile // boundaries (and so they rarely vary by sub-pixel amounts), just - // check that values are within a pixel of the display-port bounds. + // check that values are within a couple of pixels of the + // display-port bounds. // Never abort drawing if we can't be sure we've sent a more recent // display-port. If we abort updating when we shouldn't, we can end up // with blank regions on the screen and we open up the risk of entering // an endless updating cycle. - if (Math.abs(displayPort.getLeft() - x) <= 1 && - Math.abs(displayPort.getTop() - y) <= 1 && - Math.abs(displayPort.getBottom() - (y + height)) <= 1 && - Math.abs(displayPort.getRight() - (x + width)) <= 1) { + if (Math.abs(displayPort.getLeft() - x) <= 2 && + Math.abs(displayPort.getTop() - y) <= 2 && + Math.abs(displayPort.getBottom() - (y + height)) <= 2 && + Math.abs(displayPort.getRight() - (x + width)) <= 2) { return mProgressiveUpdateData; } From 7805230a8b11496195267798d99c7b6c2ccbaee5 Mon Sep 17 00:00:00 2001 From: Mounir Lamouri Date: Wed, 7 Nov 2012 18:10:40 +0000 Subject: [PATCH 51/77] Bug 795715 - Remove B2G-specific stuff from nsIDOMWindow. r=khuey,fabrice sr=smaug --- b2g/confvars.sh | 1 + configure.in | 4 ++++ content/events/public/nsEventNameList.h | 3 +++ content/events/src/nsEventListenerManager.cpp | 12 +++++++++++- dom/base/nsDOMClassInfo.cpp | 16 ++++++++++++++++ dom/base/nsGlobalWindow.cpp | 9 +++++++++ dom/base/nsGlobalWindow.h | 14 ++++++++++++++ dom/base/nsPIDOMWindow.h | 2 ++ dom/interfaces/base/Makefile.in | 6 ++++++ dom/interfaces/base/nsIDOMWindow.idl | 6 +----- dom/interfaces/base/nsIDOMWindowB2G.idl | 14 ++++++++++++++ 11 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 dom/interfaces/base/nsIDOMWindowB2G.idl diff --git a/b2g/confvars.sh b/b2g/confvars.sh index c3d009e189c..71a73a993bc 100755 --- a/b2g/confvars.sh +++ b/b2g/confvars.sh @@ -46,3 +46,4 @@ MOZ_TIME_MANAGER=1 MOZ_PAY=1 MOZ_TOOLKIT_SEARCH= +MOZ_B2G=1 diff --git a/configure.in b/configure.in index 5bb372e5a02..763535e21cb 100644 --- a/configure.in +++ b/configure.in @@ -4415,11 +4415,15 @@ browser) xulrunner) AC_DEFINE(MOZ_XULRUNNER) ;; +b2g) + AC_DEFINE(MOZ_B2G) + ;; esac AC_SUBST(MOZ_BUILD_APP) AC_SUBST(MOZ_PHOENIX) AC_SUBST(MOZ_XULRUNNER) +AC_SUBST(MOZ_B2G) AC_DEFINE_UNQUOTED(MOZ_BUILD_APP,$MOZ_BUILD_APP) diff --git a/content/events/public/nsEventNameList.h b/content/events/public/nsEventNameList.h index 51487e098ef..b74a8f2bb76 100644 --- a/content/events/public/nsEventNameList.h +++ b/content/events/public/nsEventNameList.h @@ -463,6 +463,8 @@ WINDOW_ONLY_EVENT(devicelight, NS_DEVICE_LIGHT, EventNameType_None, NS_EVENT) + +#ifdef MOZ_B2G WINDOW_ONLY_EVENT(moztimechange, NS_MOZ_TIME_CHANGE_EVENT, EventNameType_None, @@ -475,6 +477,7 @@ WINDOW_ONLY_EVENT(moznetworkdownload, NS_NETWORK_DOWNLOAD_EVENT, EventNameType_None, NS_EVENT) +#endif // MOZ_B2G TOUCH_EVENT(touchstart, NS_TOUCH_START, diff --git a/content/events/src/nsEventListenerManager.cpp b/content/events/src/nsEventListenerManager.cpp index 6160efa00f0..a21d5f89187 100644 --- a/content/events/src/nsEventListenerManager.cpp +++ b/content/events/src/nsEventListenerManager.cpp @@ -286,6 +286,7 @@ nsEventListenerManager::AddEventListener(nsIDOMEventListener *aListener, EnableDevice(NS_DEVICE_LIGHT); } else if (aTypeAtom == nsGkAtoms::ondevicemotion) { EnableDevice(NS_DEVICE_MOTION); +#ifdef MOZ_B2G } else if (aTypeAtom == nsGkAtoms::onmoztimechange) { nsCOMPtr window = GetTargetAsInnerWindow(); if (window) { @@ -301,6 +302,7 @@ nsEventListenerManager::AddEventListener(nsIDOMEventListener *aListener, if (window) { window->EnableNetworkEvent(NS_NETWORK_DOWNLOAD_EVENT); } +#endif // MOZ_B2G } else if (aTypeAtom == nsGkAtoms::ontouchstart || aTypeAtom == nsGkAtoms::ontouchend || aTypeAtom == nsGkAtoms::ontouchmove || @@ -422,9 +424,11 @@ nsEventListenerManager::RemoveEventListener(nsIDOMEventListener *aListener, uint32_t count = mListeners.Length(); uint32_t typeCount = 0; bool deviceType = IsDeviceType(aType); +#ifdef MOZ_B2G bool timeChangeEvent = (aType == NS_MOZ_TIME_CHANGE_EVENT); bool networkEvent = (aType == NS_NETWORK_UPLOAD_EVENT || aType == NS_NETWORK_DOWNLOAD_EVENT); +#endif // MOZ_B2G for (uint32_t i = 0; i < count; ++i) { ls = &mListeners.ElementAt(i); @@ -438,7 +442,11 @@ nsEventListenerManager::RemoveEventListener(nsIDOMEventListener *aListener, mNoListenerForEvent = NS_EVENT_TYPE_NULL; mNoListenerForEventAtom = nullptr; - if (!deviceType && !timeChangeEvent && !networkEvent) { + if (!deviceType +#ifdef MOZ_B2G + && !timeChangeEvent && !networkEvent +#endif // MOZ_B2G + ) { return; } --typeCount; @@ -448,6 +456,7 @@ nsEventListenerManager::RemoveEventListener(nsIDOMEventListener *aListener, if (!aAllEvents && deviceType && typeCount == 0) { DisableDevice(aType); +#ifdef MOZ_B2G } else if (timeChangeEvent && typeCount == 0) { nsCOMPtr window = GetTargetAsInnerWindow(); if (window) { @@ -458,6 +467,7 @@ nsEventListenerManager::RemoveEventListener(nsIDOMEventListener *aListener, if (window) { window->DisableNetworkEvent(aType); } +#endif // MOZ_B2G } } diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index 0cb0637f2dd..47e7cfc3f75 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -2375,6 +2375,21 @@ nsDOMClassInfo::RegisterExternalClasses() DOM_CLASSINFO_MAP_ENTRY(nsIDOMUIEvent) \ DOM_CLASSINFO_EVENT_MAP_ENTRIES +#ifdef MOZ_B2G +#define DOM_CLASSINFO_WINDOW_MAP_ENTRIES(_support_indexed_db) \ + DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow) \ + DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindowB2G) \ + DOM_CLASSINFO_MAP_ENTRY(nsIDOMJSWindow) \ + DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget) \ + DOM_CLASSINFO_MAP_ENTRY(nsIInlineEventHandlers) \ + DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIDOMStorageIndexedDB, \ + _support_indexed_db) \ + DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIDOMWindowPerformance, \ + nsGlobalWindow::HasPerformanceSupport()) \ + DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsITouchEventReceiver, \ + nsDOMTouchEvent::PrefEnabled()) \ + DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIWindowCrypto, domCryptoEnabled) +#else // !MOZ_B2G #define DOM_CLASSINFO_WINDOW_MAP_ENTRIES(_support_indexed_db) \ DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow) \ DOM_CLASSINFO_MAP_ENTRY(nsIDOMJSWindow) \ @@ -2387,6 +2402,7 @@ nsDOMClassInfo::RegisterExternalClasses() DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsITouchEventReceiver, \ nsDOMTouchEvent::PrefEnabled()) \ DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIWindowCrypto, domCryptoEnabled) +#endif // MOZ_B2G nsresult nsDOMClassInfo::Init() diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 2ee170e0c59..ebeb02dce6e 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1029,8 +1029,10 @@ nsGlobalWindow::CleanUp(bool aIgnoreModalDialog) os->RemoveObserver(mObserver, "dom-storage2-changed"); } +#ifdef MOZ_B2G DisableNetworkEvent(NS_NETWORK_UPLOAD_EVENT); DisableNetworkEvent(NS_NETWORK_DOWNLOAD_EVENT); +#endif // MOZ_B2G if (mIdleService) { mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S); @@ -1229,6 +1231,9 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindow) // Make sure this matches the cast in nsGlobalWindow::FromWrapper() NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptGlobalObject) NS_INTERFACE_MAP_ENTRY(nsIDOMWindow) +#ifdef MOZ_B2G + NS_INTERFACE_MAP_ENTRY(nsIDOMWindowB2G) +#endif // MOZ_B2G NS_INTERFACE_MAP_ENTRY(nsIDOMJSWindow) if (aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) { foundInterface = static_cast(this); @@ -9153,6 +9158,7 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic, return NS_OK; } +#ifdef MOZ_B2G if (!nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC) || !nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC)) { nsRefPtr event = new nsDOMEvent(nullptr, nullptr); @@ -9169,6 +9175,7 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic, bool dummy; return DispatchEvent(event, &dummy); } +#endif // MOZ_B2G NS_WARNING("unrecognized topic in nsGlobalWindow::Observe"); return NS_ERROR_FAILURE; @@ -11224,6 +11231,7 @@ nsGlobalWindow::SetHasAudioAvailableEventListeners() } } +#ifdef MOZ_B2G void nsGlobalWindow::EnableNetworkEvent(uint32_t aType) { @@ -11275,6 +11283,7 @@ nsGlobalWindow::DisableNetworkEvent(uint32_t aType) break; } } +#endif // MOZ_B2G #define EVENT(name_, id_, type_, struct_) \ NS_IMETHODIMP nsGlobalWindow::GetOn##name_(JSContext *cx, \ diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index dbbcbafceb8..a84c02c6908 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -71,6 +71,10 @@ // JS includes #include "jsapi.h" +#ifdef MOZ_B2G +#include "nsIDOMWindowB2G.h" +#endif // MOZ_B2G + #define DEFAULT_HOME_PAGE "www.mozilla.org" #define PREF_BROWSER_STARTUP_HOMEPAGE "browser.startup.homepage" @@ -264,6 +268,9 @@ class nsGlobalWindow : public nsPIDOMWindow, public nsITouchEventReceiver, public nsIInlineEventHandlers, public nsIWindowCrypto +#ifdef MOZ_B2G + , public nsIDOMWindowB2G +#endif // MOZ_B2G { public: friend class nsDOMMozURLProperty; @@ -313,6 +320,11 @@ public: // nsIDOMWindow NS_DECL_NSIDOMWINDOW +#ifdef MOZ_B2G + // nsIDOMWindowB2G + NS_DECL_NSIDOMWINDOWB2G +#endif // MOZ_B2G + // nsIDOMWindowPerformance NS_DECL_NSIDOMWINDOWPERFORMANCE @@ -537,8 +549,10 @@ public: virtual void EnableTimeChangeNotifications(); virtual void DisableTimeChangeNotifications(); +#ifdef MOZ_B2G virtual void EnableNetworkEvent(uint32_t aType); virtual void DisableNetworkEvent(uint32_t aType); +#endif // MOZ_B2G virtual nsresult SetArguments(nsIArray *aArguments, nsIPrincipal *aOrigin); diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h index ceeca57c45e..06c2320d0ee 100644 --- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -574,6 +574,7 @@ public: virtual void EnableTimeChangeNotifications() = 0; virtual void DisableTimeChangeNotifications() = 0; +#ifdef MOZ_B2G /** * Tell the window that it should start to listen to the network event of the * given aType. @@ -585,6 +586,7 @@ public: * given aType. */ virtual void DisableNetworkEvent(uint32_t aType) = 0; +#endif // MOZ_B2G /** * Set a arguments for this window. This will be set on the window diff --git a/dom/interfaces/base/Makefile.in b/dom/interfaces/base/Makefile.in index 7836f673b83..64ebda37f07 100644 --- a/dom/interfaces/base/Makefile.in +++ b/dom/interfaces/base/Makefile.in @@ -57,6 +57,12 @@ XPIDLSRCS = \ nsIIdleObserver.idl \ $(NULL) +ifdef MOZ_B2G +XPIDLSRCS += \ + nsIDOMWindowB2G.idl \ + $(NULL) +endif + include $(topsrcdir)/config/rules.mk XPIDL_FLAGS += \ diff --git a/dom/interfaces/base/nsIDOMWindow.idl b/dom/interfaces/base/nsIDOMWindow.idl index 29302b656b7..26b9b680707 100644 --- a/dom/interfaces/base/nsIDOMWindow.idl +++ b/dom/interfaces/base/nsIDOMWindow.idl @@ -32,7 +32,7 @@ interface nsIDOMMozURLProperty : nsISupports * @see */ -[scriptable, uuid(148ab425-5d38-40fd-9fe0-0eae9fed81df)] +[scriptable, uuid(1534ecd7-e298-420e-9063-e6c2d1243d49)] interface nsIDOMWindow : nsISupports { // the current browsing context @@ -496,13 +496,9 @@ interface nsIDOMWindow : nsISupports [implicit_jscontext] attribute jsval ondeviceproximity; [implicit_jscontext] attribute jsval onuserproximity; [implicit_jscontext] attribute jsval ondevicelight; - [implicit_jscontext] attribute jsval onmoztimechange; [implicit_jscontext] attribute jsval onmouseenter; [implicit_jscontext] attribute jsval onmouseleave; - - [implicit_jscontext] attribute jsval onmoznetworkupload; - [implicit_jscontext] attribute jsval onmoznetworkdownload; }; [scriptable, uuid(2146c906-57f7-486c-a1b4-8cdb57ef577f)] diff --git a/dom/interfaces/base/nsIDOMWindowB2G.idl b/dom/interfaces/base/nsIDOMWindowB2G.idl new file mode 100644 index 00000000000..f5bfcc90dbd --- /dev/null +++ b/dom/interfaces/base/nsIDOMWindowB2G.idl @@ -0,0 +1,14 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(96bde2ae-9b74-4f4d-b674-664a67a35b7e)] +interface nsIDOMWindowB2G : nsISupports +{ + [implicit_jscontext] attribute jsval onmoztimechange; + [implicit_jscontext] attribute jsval onmoznetworkupload; + [implicit_jscontext] attribute jsval onmoznetworkdownload; +}; \ No newline at end of file From a2d719de86753570ad89f64ae6ae4d40745bb58f Mon Sep 17 00:00:00 2001 From: Gina Yeh Date: Wed, 7 Nov 2012 10:12:41 -0800 Subject: [PATCH 52/77] Bug 808879 - Patch 1: Expose connection status for system app, r=qdot, sr=mrbkap --- b2g/chrome/content/shell.js | 2 +- dom/bluetooth/BluetoothHfpManager.cpp | 2 +- dom/bluetooth/BluetoothManager.cpp | 13 +++++++++++++ dom/bluetooth/BluetoothService.h | 3 +++ .../ipc/BluetoothServiceChildProcess.cpp | 7 +++++++ .../ipc/BluetoothServiceChildProcess.h | 3 +++ dom/bluetooth/linux/BluetoothDBusService.cpp | 17 +++++++++++++++++ dom/bluetooth/linux/BluetoothDBusService.h | 3 +++ dom/bluetooth/nsIDOMBluetoothManager.idl | 4 +++- 9 files changed, 51 insertions(+), 3 deletions(-) diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index 631d49eb183..c73d8507abe 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -519,7 +519,7 @@ Services.obs.addObserver(function onWebappsReady(subject, topic, data) { Services.obs.addObserver(function onBluetoothVolumeChange(subject, topic, data) { shell.sendChromeEvent({ - type: "volumeset", + type: "bluetooth-volumeset", value: data }); }, 'bluetooth-volume-change', false); diff --git a/dom/bluetooth/BluetoothHfpManager.cpp b/dom/bluetooth/BluetoothHfpManager.cpp index dfcede0a5e7..2807b44f601 100644 --- a/dom/bluetooth/BluetoothHfpManager.cpp +++ b/dom/bluetooth/BluetoothHfpManager.cpp @@ -25,7 +25,7 @@ #include /* usleep() */ #define MOZSETTINGS_CHANGED_ID "mozsettings-changed" -#define AUDIO_VOLUME_MASTER "audio.volume.master" +#define AUDIO_VOLUME_MASTER "audio.volume.bt_sco" #define HANDSFREE_UUID mozilla::dom::bluetooth::BluetoothServiceUuidStr::Handsfree #define HEADSET_UUID mozilla::dom::bluetooth::BluetoothServiceUuidStr::Headset diff --git a/dom/bluetooth/BluetoothManager.cpp b/dom/bluetooth/BluetoothManager.cpp index eddd1b7e0f8..f5dd76f7d58 100644 --- a/dom/bluetooth/BluetoothManager.cpp +++ b/dom/bluetooth/BluetoothManager.cpp @@ -266,6 +266,19 @@ BluetoothManager::Notify(const BluetoothSignal& aData) } } +NS_IMETHODIMP +BluetoothManager::IsConnected(uint16_t aProfileId, bool* aConnected) +{ + BluetoothService* bs = BluetoothService::Get(); + if (!bs) { + NS_WARNING("BluetoothService not available!"); + return NS_ERROR_FAILURE; + } + + *aConnected = bs->IsConnected(aProfileId); + return NS_OK; +} NS_IMPL_EVENT_HANDLER(BluetoothManager, enabled) NS_IMPL_EVENT_HANDLER(BluetoothManager, disabled) NS_IMPL_EVENT_HANDLER(BluetoothManager, adapteradded) + diff --git a/dom/bluetooth/BluetoothService.h b/dom/bluetooth/BluetoothService.h index 1ecb57bf524..56e82d9dd18 100644 --- a/dom/bluetooth/BluetoothService.h +++ b/dom/bluetooth/BluetoothService.h @@ -273,6 +273,9 @@ public: virtual void Disconnect(uint16_t aProfileId, BluetoothReplyRunnable* aRunnable) = 0; + virtual bool + IsConnected(uint16_t aProfileId) = 0; + virtual void SendFile(const nsAString& aDeviceAddress, BlobParent* aBlobParent, diff --git a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp index 0f87da2d76f..1ded160308e 100644 --- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp +++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp @@ -391,3 +391,10 @@ BluetoothServiceChildProcess::StopInternal() MOZ_NOT_REACHED("This should never be called!"); return NS_ERROR_FAILURE; } + +bool +BluetoothServiceChildProcess::IsConnected(uint16_t aProfileId) +{ + MOZ_NOT_REACHED("This should never be called!"); + return false; +} diff --git a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h index be4db2c4d09..3bc7db8e4f5 100644 --- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h +++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h @@ -139,6 +139,9 @@ public: Disconnect(const uint16_t aProfileId, BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE; + virtual bool + IsConnected(uint16_t aProfileId) MOZ_OVERRIDE; + virtual void SendFile(const nsAString& aDeviceAddress, BlobParent* aBlobParent, diff --git a/dom/bluetooth/linux/BluetoothDBusService.cpp b/dom/bluetooth/linux/BluetoothDBusService.cpp index c4563dee728..a677a91155a 100644 --- a/dom/bluetooth/linux/BluetoothDBusService.cpp +++ b/dom/bluetooth/linux/BluetoothDBusService.cpp @@ -2387,6 +2387,23 @@ BluetoothDBusService::Disconnect(const uint16_t aProfileId, DispatchBluetoothReply(aRunnable, v, replyError); } +bool +BluetoothDBusService::IsConnected(const uint16_t aProfileId) +{ + NS_ASSERTION(NS_IsMainThread(), "Must be called from main thread!"); + + if (aProfileId == (uint16_t)(BluetoothServiceUuid::Handsfree >> 32) + || aProfileId == (uint16_t)(BluetoothServiceUuid::Headset >> 32)) { + BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); + return hfp->GetConnectionStatus() == SocketConnectionStatus::SOCKET_CONNECTED; + } else if (aProfileId == (uint16_t)(BluetoothServiceUuid::ObjectPush >> 32)) { + BluetoothOppManager* opp = BluetoothOppManager::Get(); + return opp->GetConnectionStatus() == SocketConnectionStatus::SOCKET_CONNECTED; + } + + return false; +} + class ConnectBluetoothSocketRunnable : public nsRunnable { public: diff --git a/dom/bluetooth/linux/BluetoothDBusService.h b/dom/bluetooth/linux/BluetoothDBusService.h index e7a0edcd1ee..14e9c16b0b6 100644 --- a/dom/bluetooth/linux/BluetoothDBusService.h +++ b/dom/bluetooth/linux/BluetoothDBusService.h @@ -139,6 +139,9 @@ public: const uint16_t aProfileId, BluetoothReplyRunnable* aRunnable); + virtual bool + IsConnected(uint16_t aProfileId); + virtual void Disconnect(const uint16_t aProfileId, BluetoothReplyRunnable* aRunnable); diff --git a/dom/bluetooth/nsIDOMBluetoothManager.idl b/dom/bluetooth/nsIDOMBluetoothManager.idl index 50b53ad4f2c..d2c80c750b9 100644 --- a/dom/bluetooth/nsIDOMBluetoothManager.idl +++ b/dom/bluetooth/nsIDOMBluetoothManager.idl @@ -9,13 +9,15 @@ interface nsIDOMDOMRequest; interface nsIDOMBluetoothAdapter; -[scriptable, builtinclass, uuid(d27ec867-949f-4585-b718-d2352e420ec6)] +[scriptable, builtinclass, uuid(3300693f-ae91-4a3f-b887-bf502c6a97ee)] interface nsIDOMBluetoothManager : nsIDOMEventTarget { readonly attribute bool enabled; nsIDOMDOMRequest getDefaultAdapter(); + bool isConnected(in unsigned short aProfile); + [implicit_jscontext] attribute jsval onenabled; [implicit_jscontext] attribute jsval ondisabled; [implicit_jscontext] attribute jsval onadapteradded; From 63cf0541089bdfe6af8d8637f1a8b6cae12985df Mon Sep 17 00:00:00 2001 From: Yoshi Huang Date: Mon, 5 Nov 2012 10:25:54 -0800 Subject: [PATCH 53/77] Bug 807265 - B2G STK: implement RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING. r=vicamo --- dom/system/gonk/ril_worker.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 6c1cfe3304a..f5e703d0939 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -2894,6 +2894,13 @@ let RIL = { return numbers.indexOf(number) != -1; }, + /** + * Report STK Service is running. + */ + reportStkServiceIsRunning: function reportStkServiceIsRunning() { + Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING); + }, + /** * Process ICC status. */ @@ -2961,6 +2968,7 @@ let RIL = { this.getSignalStrength(); if (newCardState == GECKO_CARDSTATE_READY) { this.fetchICCRecords(); + this.reportStkServiceIsRunning(); } this.cardState = newCardState; From 2566a9e1723d35bd05d60751322cf8bfc0e5d278 Mon Sep 17 00:00:00 2001 From: Bas Schouten Date: Wed, 7 Nov 2012 09:29:54 +0100 Subject: [PATCH 54/77] Bug 509052: Add new, faster blurring code. r=derf --- gfx/2d/Blur.cpp | 262 +++++++++++++++++++++++++++++++----- gfx/2d/Blur.h | 8 ++ gfx/2d/BlurSSE2.cpp | 250 ++++++++++++++++++++++++++++++++++ gfx/2d/ImageScalingSSE2.cpp | 14 +- gfx/2d/Makefile.in | 7 +- gfx/2d/SSEHelpers.h | 17 +++ gfx/2d/Tools.h | 58 ++++++++ 7 files changed, 571 insertions(+), 45 deletions(-) create mode 100644 gfx/2d/BlurSSE2.cpp create mode 100644 gfx/2d/SSEHelpers.h diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp index 8def843f3f4..32bb576d0f2 100644 --- a/gfx/2d/Blur.cpp +++ b/gfx/2d/Blur.cpp @@ -12,6 +12,9 @@ #include "mozilla/Constants.h" #include "mozilla/Util.h" +#include "2D.h" +#include "Tools.h" + using namespace std; namespace mozilla { @@ -311,8 +314,8 @@ SpreadVertical(unsigned char* aInput, } } -static CheckedInt -RoundUpToMultipleOf4(int32_t aVal) +CheckedInt +AlphaBoxBlur::RoundUpToMultipleOf4(int32_t aVal) { CheckedInt val(aVal); @@ -378,10 +381,11 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, if (stride.isValid()) { mStride = stride.value(); - CheckedInt size = CheckedInt(mStride) * mRect.height * - sizeof(unsigned char); + // We need to leave room for an additional 3 bytes for a potential overrun + // in our blurring code. + CheckedInt size = CheckedInt(mStride) * mRect.height + 3; if (size.isValid()) { - mData = static_cast(malloc(size.value())); + mData = new uint8_t[size.value()]; memset(mData, 0, size.value()); } } @@ -405,7 +409,7 @@ AlphaBoxBlur::AlphaBoxBlur(uint8_t* aData, AlphaBoxBlur::~AlphaBoxBlur() { if (mFreeData) { - free(mData); + delete [] mData; } } @@ -455,42 +459,238 @@ AlphaBoxBlur::Blur() if (mBlurRadius != IntSize(0,0) || mSpreadRadius != IntSize(0,0)) { int32_t stride = GetStride(); - // No need to use CheckedInt here - we have validated it in the constructor. - size_t szB = stride * GetSize().height * sizeof(unsigned char); - unsigned char* tmpData = static_cast(malloc(szB)); - if (!tmpData) - return; // OOM - - memset(tmpData, 0, szB); + IntSize size = GetSize(); if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) { + // No need to use CheckedInt here - we have validated it in the constructor. + size_t szB = stride * size.height; + unsigned char* tmpData = new uint8_t[szB]; + + memset(tmpData, 0, szB); + SpreadHorizontal(mData, tmpData, mSpreadRadius.width, GetSize().width, GetSize().height, stride, mSkipRect); SpreadVertical(tmpData, mData, mSpreadRadius.height, GetSize().width, GetSize().height, stride, mSkipRect); + + delete [] tmpData; } - if (mBlurRadius.width > 0) { - int32_t lobes[3][2]; - ComputeLobes(mBlurRadius.width, lobes); - BoxBlurHorizontal(mData, tmpData, lobes[0][0], lobes[0][1], stride, GetSize().height, mSkipRect); - BoxBlurHorizontal(tmpData, mData, lobes[1][0], lobes[1][1], stride, GetSize().height, mSkipRect); - BoxBlurHorizontal(mData, tmpData, lobes[2][0], lobes[2][1], stride, GetSize().height, mSkipRect); + int32_t horizontalLobes[3][2]; + ComputeLobes(mBlurRadius.width, horizontalLobes); + int32_t verticalLobes[3][2]; + ComputeLobes(mBlurRadius.height, verticalLobes); + + // We want to allow for some extra space on the left for alignment reasons. + int32_t maxLeftLobe = RoundUpToMultipleOf4(horizontalLobes[0][0] + 1).value(); + + IntSize integralImageSize(size.width + maxLeftLobe + horizontalLobes[1][1], + size.height + verticalLobes[0][0] + verticalLobes[1][1] + 1); + +#ifdef IS_BIG_ENDIAN + const bool cIsBigEndian = true; +#else + const bool cIsBigEndian = false; +#endif + + if (cIsBigEndian || (integralImageSize.width * integralImageSize.height) > (1 << 24)) { + // Fallback to old blurring code when the surface is so large it may + // overflow our integral image! + + // No need to use CheckedInt here - we have validated it in the constructor. + size_t szB = stride * size.height; + unsigned char* tmpData = new uint8_t[szB]; + + memset(tmpData, 0, szB); + + if (mBlurRadius.width > 0) { + BoxBlurHorizontal(mData, tmpData, horizontalLobes[0][0], horizontalLobes[0][1], stride, GetSize().height, mSkipRect); + BoxBlurHorizontal(tmpData, mData, horizontalLobes[1][0], horizontalLobes[1][1], stride, GetSize().height, mSkipRect); + BoxBlurHorizontal(mData, tmpData, horizontalLobes[2][0], horizontalLobes[2][1], stride, GetSize().height, mSkipRect); + } else { + uint8_t *tmp = mData; + mData = tmpData; + tmpData = tmp; + } + if (mBlurRadius.height > 0) { + BoxBlurVertical(tmpData, mData, verticalLobes[0][0], verticalLobes[0][1], stride, GetSize().height, mSkipRect); + BoxBlurVertical(mData, tmpData, verticalLobes[1][0], verticalLobes[1][1], stride, GetSize().height, mSkipRect); + BoxBlurVertical(tmpData, mData, verticalLobes[2][0], verticalLobes[2][1], stride, GetSize().height, mSkipRect); + } else { + uint8_t *tmp = mData; + mData = tmpData; + tmpData = tmp; + } + + delete [] tmpData; } else { - memcpy(tmpData, mData, stride * GetSize().height); - } + size_t integralImageStride = GetAlignedStride<16>(integralImageSize.width * 4); - if (mBlurRadius.height > 0) { - int32_t lobes[3][2]; - ComputeLobes(mBlurRadius.height, lobes); - BoxBlurVertical(tmpData, mData, lobes[0][0], lobes[0][1], stride, GetSize().height, mSkipRect); - BoxBlurVertical(mData, tmpData, lobes[1][0], lobes[1][1], stride, GetSize().height, mSkipRect); - BoxBlurVertical(tmpData, mData, lobes[2][0], lobes[2][1], stride, GetSize().height, mSkipRect); - } else { - memcpy(mData, tmpData, stride * GetSize().height); - } + // We need to leave room for an additional 12 bytes for a maximum overrun + // of 3 pixels in the blurring code. + AlignedArray integralImage((integralImageStride / 4) * integralImageSize.height + 12); - free(tmpData); +#ifdef USE_SSE2 + if (Factory::HasSSE2()) { + BoxBlur_SSE2(horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], + verticalLobes[0][1], integralImage, integralImageStride); + BoxBlur_SSE2(horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], + verticalLobes[1][1], integralImage, integralImageStride); + BoxBlur_SSE2(horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], + verticalLobes[2][1], integralImage, integralImageStride); + } else +#endif + { + BoxBlur_C(horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], + verticalLobes[0][1], integralImage, integralImageStride); + BoxBlur_C(horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], + verticalLobes[1][1], integralImage, integralImageStride); + BoxBlur_C(horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], + verticalLobes[2][1], integralImage, integralImageStride); + } + } + } +} + +MOZ_ALWAYS_INLINE void +GenerateIntegralRow(uint32_t *aDest, const uint8_t *aSource, uint32_t *aPreviousRow, + const uint32_t &aSourceWidth, const uint32_t &aLeftInflation, const uint32_t &aRightInflation) +{ + uint32_t currentRowSum = 0; + uint32_t pixel = aSource[0]; + for (uint32_t x = 0; x < aLeftInflation; x++) { + currentRowSum += pixel; + *aDest++ = currentRowSum + *aPreviousRow++; + } + for (uint32_t x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x += 4) { + uint32_t alphaValues = *(uint32_t*)(aSource + (x - aLeftInflation)); + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + } + pixel = aSource[aSourceWidth - 1]; + for (uint32_t x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += pixel; + *aDest++ = currentRowSum + *aPreviousRow++; + } +} + +MOZ_ALWAYS_INLINE void +GenerateIntegralImage_C(int32_t aLeftInflation, int32_t aRightInflation, + int32_t aTopInflation, int32_t aBottomInflation, + uint32_t *aIntegralImage, size_t aIntegralImageStride, + uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) +{ + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + memset(aIntegralImage, 0, aIntegralImageStride); + + GenerateIntegralRow(aIntegralImage, aSource, aIntegralImage, + aSize.width, aLeftInflation, aRightInflation); + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t *intFirstRow = aIntegralImage; + + GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource, aIntegralImage + (y - 1) * stride32bit, + aSize.width, aLeftInflation, aRightInflation); } + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource + aSourceStride * (y - aTopInflation), + aIntegralImage + (y - 1) * stride32bit, aSize.width, aLeftInflation, aRightInflation); + } + + if (aBottomInflation) { + for (int y = (aSize.height + aTopInflation); y < integralImageSize.height; y++) { + GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource + ((aSize.height - 1) * aSourceStride), + aIntegralImage + (y - 1) * stride32bit, + aSize.width, aLeftInflation, aRightInflation); + } + } +} + +/** + * Attempt to do an in-place box blur using an integral image. + */ +void +AlphaBoxBlur::BoxBlur_C(int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aTopLobe, + int32_t aBottomLobe, + uint32_t *aIntegralImage, + size_t aIntegralImageStride) +{ + IntSize size = GetSize(); + + MOZ_ASSERT(size.width > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t stride32bit = aIntegralImageStride / 4; + + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_C(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, mData, + mStride, size); + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + // Storing these locally makes this about 30% faster! Presumably the compiler + // can't be sure we're not altering the member variables in this loop. + IntRect skipRect = mSkipRect; + uint8_t *data = mData; + int32_t stride = mStride; + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + + uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * stride32bit - aLeftLobe); + uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * stride32bit + aRightLobe); + uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * stride32bit + aRightLobe); + uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * stride32bit - aLeftLobe); + + for (int32_t x = 0; x < size.width; x++) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 1; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + int32_t topLeft = topLeftBase[x]; + int32_t topRight = topRightBase[x]; + int32_t bottomRight = bottomRightBase[x]; + int32_t bottomLeft = bottomLeftBase[x]; + + uint32_t value = bottomRight - topRight - bottomLeft; + value += topLeft; + + data[stride * y + x] = (uint64_t(reciprocal) * value) >> 32; + } + } } /** diff --git a/gfx/2d/Blur.h b/gfx/2d/Blur.h index 24f32941904..d425d6ee9f2 100644 --- a/gfx/2d/Blur.h +++ b/gfx/2d/Blur.h @@ -7,6 +7,7 @@ #include "mozilla/gfx/Rect.h" #include "mozilla/gfx/Point.h" +#include "mozilla/CheckedInt.h" namespace mozilla { namespace gfx { @@ -114,6 +115,13 @@ public: private: + void BoxBlur_C(int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); + void BoxBlur_SSE2(int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); + + static CheckedInt RoundUpToMultipleOf4(int32_t aVal); + /** * A rect indicating the area where blurring is unnecessary, and the blur * algorithm should skip over it. diff --git a/gfx/2d/BlurSSE2.cpp b/gfx/2d/BlurSSE2.cpp new file mode 100644 index 00000000000..d53153d49be --- /dev/null +++ b/gfx/2d/BlurSSE2.cpp @@ -0,0 +1,250 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Blur.h" + +#include "SSEHelpers.h" + +#include + +namespace mozilla { +namespace gfx { + +MOZ_ALWAYS_INLINE +uint32_t DivideAndPack(__m128i aValues, __m128i aDivisor, __m128i aMask) +{ + __m128i multiplied = _mm_srli_epi64(_mm_mul_epu32(aValues, aDivisor), 32); // 00p300p1 + multiplied = _mm_or_si128(multiplied, _mm_and_si128(_mm_mul_epu32(_mm_srli_epi64(aValues, 32), aDivisor), + aMask)); // p4p3p2p1 + __m128i final = _mm_packus_epi16(_mm_packs_epi32(multiplied, _mm_setzero_si128()), _mm_setzero_si128()); + + return _mm_cvtsi128_si32(final); +} + +MOZ_ALWAYS_INLINE +void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource, + int32_t aSourceWidth, int32_t aLeftInflation, + int32_t aRightInflation) +{ + int32_t currentRowSum = 0; + + for (int x = 0; x < aLeftInflation; x++) { + currentRowSum += aSource[0]; + aDest[x] = currentRowSum; + } + for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { + currentRowSum += aSource[(x - aLeftInflation)]; + aDest[x] = currentRowSum; + } + for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += aSource[aSourceWidth - 1]; + aDest[x] = currentRowSum; + } +} + +// This function calculates an integral of four pixels stored in the 4 +// 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns +// { 30, 80, 160, 260 }. This seems to be the fastest way to do this after +// much testing. +MOZ_ALWAYS_INLINE +__m128i AccumulatePixelSums(__m128i aPixels) +{ + __m128i sumPixels = aPixels; + __m128i currentPixels = _mm_slli_si128(aPixels, 4); + sumPixels = _mm_add_epi32(sumPixels, currentPixels); + currentPixels = _mm_unpacklo_epi64(_mm_setzero_si128(), sumPixels); + + return _mm_add_epi32(sumPixels, currentPixels); +} + +MOZ_ALWAYS_INLINE void +GenerateIntegralImage_SSE2(int32_t aLeftInflation, int32_t aRightInflation, + int32_t aTopInflation, int32_t aBottomInflation, + uint32_t *aIntegralImage, size_t aIntegralImageStride, + uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) +{ + MOZ_ASSERT(!(aLeftInflation & 3)); + + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation); + + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t *intFirstRow = aIntegralImage; + + for (int x = 0; x < integralImageSize.width; x += 4) { + __m128i firstRow = _mm_load_si128((__m128i*)(intFirstRow + x)); + __m128i previousRow = _mm_load_si128((__m128i*)(intPrevRow + x)); + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(firstRow, previousRow)); + } + } + + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + __m128i currentRowSum = _mm_setzero_si128(); + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation); + + uint32_t pixel = sourceRow[0]; + for (int x = 0; x < aLeftInflation; x += 4) { + __m128i sumPixels = AccumulatePixelSums(_mm_shuffle_epi32(_mm_set1_epi32(pixel), _MM_SHUFFLE(0, 0, 0, 0))); + + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); + + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { + uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation)); + + // It's important to shuffle here. When we exit this loop currentRowSum + // has to be set to sumPixels, so that the following loop can get the + // correct pixel for the currentRowSum. The highest order pixel in + // currentRowSum could've originated from accumulation in the stride. + currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); + + __m128i sumPixels = AccumulatePixelSums(_mm_unpacklo_epi16(_mm_unpacklo_epi8( _mm_set1_epi32(pixels), _mm_setzero_si128()), _mm_setzero_si128())); + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = sumPixels; + + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + + pixel = sourceRow[aSize.width - 1]; + int x = (aSize.width + aLeftInflation); + if ((aSize.width & 3)) { + // Deal with unaligned portion. Get the correct pixel from currentRowSum, + // see explanation above. + uint32_t intCurrentRowSum = ((uint32_t*)¤tRowSum)[(aSize.width % 4) - 1]; + for (; x < integralImageSize.width; x++) { + // We could be unaligned here! + if (!(x & 3)) { + // aligned! + currentRowSum = _mm_set1_epi32(intCurrentRowSum); + break; + } + intCurrentRowSum += pixel; + intRow[x] = intPrevRow[x] + intCurrentRowSum; + } + } else { + currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); + } + for (; x < integralImageSize.width; x += 4) { + __m128i sumPixels = AccumulatePixelSums(_mm_set1_epi32(pixel)); + + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); + + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + } + + if (aBottomInflation) { + // Store the last valid row of our source image in the last row of + // our integral image. This will be overwritten with the correct values + // in the upcoming loop. + LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit, + aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation); + + + for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) { + __m128i *intRow = (__m128i*)(aIntegralImage + (y * stride32bit)); + __m128i *intPrevRow = (__m128i*)(aIntegralImage + (y - 1) * stride32bit); + __m128i *intLastRow = (__m128i*)(aIntegralImage + (integralImageSize.height - 1) * stride32bit); + + for (int x = 0; x < integralImageSize.width; x += 4) { + _mm_store_si128(intRow + (x / 4), + _mm_add_epi32(_mm_load_si128(intLastRow + (x / 4)), + _mm_load_si128(intPrevRow + (x / 4)))); + } + } + } +} + +/** + * Attempt to do an in-place box blur using an integral image. + */ +void +AlphaBoxBlur::BoxBlur_SSE2(int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aTopLobe, + int32_t aBottomLobe, + uint32_t *aIntegralImage, + size_t aIntegralImageStride) +{ + IntSize size = GetSize(); + + MOZ_ASSERT(size.height > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t stride32bit = aIntegralImageStride / 4; + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_SSE2(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, mData, + mStride, size); + + __m128i divisor = _mm_set1_epi32(reciprocal); + __m128i mask = _mm_setr_epi32(0x0, 0xffffffff, 0x0, 0xffffffff); + + // This points to the start of the rectangle within the IntegralImage that overlaps + // the surface being blurred. + uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + IntRect skipRect = mSkipRect; + int32_t stride = mStride; + uint8_t *data = mData; + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + + uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + + for (int32_t x = 0; x < size.width; x += 4) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 4; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + __m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + __m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + __m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + __m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + + __m128i values = _mm_add_epi32(_mm_sub_epi32(_mm_sub_epi32(bottomRight, topRight), bottomLeft), topLeft); + + *(uint32_t*)(data + stride * y + x) = DivideAndPack(values, divisor, mask); + } + } + +} + +} +} diff --git a/gfx/2d/ImageScalingSSE2.cpp b/gfx/2d/ImageScalingSSE2.cpp index cad5eb85aae..f87653b4cab 100644 --- a/gfx/2d/ImageScalingSSE2.cpp +++ b/gfx/2d/ImageScalingSSE2.cpp @@ -6,8 +6,7 @@ #include "ImageScaling.h" #include "mozilla/Attributes.h" -#include -#include +#include "SSEHelpers.h" /* The functions below use the following system for averaging 4 pixels: * @@ -108,17 +107,6 @@ MOZ_ALWAYS_INLINE __m128i avg_sse2_8x1_4x1(__m128i a, __m128i b) return _mm_not_si128(_mm_avg_epu8(_mm_not_si128(a), _mm_not_si128(b))); } -/* Before Nehalem _mm_loadu_si128 could be very slow, this trick is a little - * faster. Once enough people are on architectures where _mm_loadu_si128 is - * fast we can migrate to it. - */ -MOZ_ALWAYS_INLINE __m128i loadUnaligned128(const __m128i *aSource) -{ - // Yes! We use uninitialized memory here, we'll overwrite it though! - __m128 res = _mm_loadl_pi(_mm_set1_ps(0), (const __m64*)aSource); - return _mm_castps_si128(_mm_loadh_pi(res, ((const __m64*)(aSource)) + 1)); -} - MOZ_ALWAYS_INLINE uint32_t Avg2x2(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { uint32_t sum = a ^ b ^ c; diff --git a/gfx/2d/Makefile.in b/gfx/2d/Makefile.in index 6959f5d2c32..376f72cfb90 100644 --- a/gfx/2d/Makefile.in +++ b/gfx/2d/Makefile.in @@ -116,7 +116,10 @@ endif ifneq (,$(INTEL_ARCHITECTURE)) # VC2005 doesn't support _mm_castsi128_ps, so SSE2 is turned off ifneq (1400,$(_MSC_VER)) -CPPSRCS += ImageScalingSSE2.cpp +CPPSRCS += \ + ImageScalingSSE2.cpp \ + BlurSSE2.cpp \ + $(NULL) DEFINES += -DUSE_SSE2 endif endif @@ -161,10 +164,12 @@ DEFINES := $(filter-out -DUNICODE -D_UNICODE,$(DEFINES)) ifneq (,$(INTEL_ARCHITECTURE)) ifdef GNU_CC ImageScalingSSE2.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 +BlurSSE2.$(OBJ_SUFFIX): CXXFLAGS+=-msse2 endif ifdef SOLARIS_SUNPRO_CXX ImageScalingSSE2.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 +BlurSSE2.$(OBJ_SUFFIX): OS_CXXFLAGS += -xarch=sse2 -xO4 endif endif diff --git a/gfx/2d/SSEHelpers.h b/gfx/2d/SSEHelpers.h new file mode 100644 index 00000000000..61b53d86eac --- /dev/null +++ b/gfx/2d/SSEHelpers.h @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include + +/* Before Nehalem _mm_loadu_si128 could be very slow, this trick is a little + * faster. Once enough people are on architectures where _mm_loadu_si128 is + * fast we can migrate to it. + */ +MOZ_ALWAYS_INLINE __m128i loadUnaligned128(const __m128i *aSource) +{ + // Yes! We use uninitialized memory here, we'll overwrite it though! + __m128 res = _mm_loadl_pi(_mm_set1_ps(0), (const __m64*)aSource); + return _mm_castps_si128(_mm_loadh_pi(res, ((const __m64*)(aSource)) + 1)); +} diff --git a/gfx/2d/Tools.h b/gfx/2d/Tools.h index d645c2b219d..60e89fa0ea2 100644 --- a/gfx/2d/Tools.h +++ b/gfx/2d/Tools.h @@ -81,6 +81,64 @@ BytesPerPixel(SurfaceFormat aFormat) } } +template +struct AlignedArray +{ + AlignedArray() + : mStorage(nullptr) + , mPtr(nullptr) + { + } + + MOZ_ALWAYS_INLINE AlignedArray(size_t aSize) + : mStorage(nullptr) + { + Realloc(aSize); + } + + MOZ_ALWAYS_INLINE ~AlignedArray() + { + delete [] mStorage; + } + + void Dealloc() + { + delete [] mStorage; + mStorage = mPtr = nullptr; + } + + MOZ_ALWAYS_INLINE void Realloc(size_t aSize) + { + delete [] mStorage; + mStorage = new T[aSize + (alignment - 1)]; + if (uintptr_t(mStorage) % alignment) { + // Our storage does not start at a -byte boundary. Make sure mData does! + mPtr = (uint32_t*)(uintptr_t(mStorage) + + (alignment - (uintptr_t(mStorage) % alignment))); + } else { + mPtr = mStorage; + } + } + + MOZ_ALWAYS_INLINE operator T*() + { + return mPtr; + } + + T *mStorage; + T *mPtr; +}; + +template +int32_t GetAlignedStride(int32_t aStride) +{ + if (aStride % alignment) { + return aStride + (alignment - (aStride % alignment)); + } + + return aStride; +} + } } From 5d55002797d7af0b3150aa859d546a72d563dc67 Mon Sep 17 00:00:00 2001 From: Andrew Halberstadt Date: Wed, 7 Nov 2012 10:15:17 -0800 Subject: [PATCH 55/77] Bug 809437 - Ensure b2g process has fully stopped before restarting it when installing gecko into an emulator, r=jgriffin --- testing/marionette/client/marionette/emulator.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/testing/marionette/client/marionette/emulator.py b/testing/marionette/client/marionette/emulator.py index 33b4d2f8cc1..968887d21d3 100644 --- a/testing/marionette/client/marionette/emulator.py +++ b/testing/marionette/client/marionette/emulator.py @@ -402,8 +402,17 @@ waitFor( print 'restarting B2G' self.dm.shellCheckOutput(['stop', 'b2g']) + # ensure the b2g process has fully stopped (bug 809437) + for i in range(0, 10): + time.sleep(1) + if self.dm.processExist('b2g') is None: + break + else: + raise TimeoutException("Timeout waiting for the b2g process to terminate") self.dm.shellCheckOutput(['start', 'b2g']) - self.wait_for_port() + + if not self.wait_for_port(): + raise TimeoutException("Timeout waiting for marionette on port '%s'" % self.marionette_port) self.wait_for_system_message(marionette) def rotate_log(self, srclog, index=1): From db03eac92b03690d104950dd8ca313b2300c00b1 Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Tue, 6 Nov 2012 11:57:42 -0800 Subject: [PATCH 56/77] Bug 501265 - SpiderMonkey's shell |load()| should interpret chars as UTF-8; rs=Waldo --- js/src/shell/js.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 82052d9aef0..53635bc6adf 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -712,7 +712,7 @@ Load(JSContext *cx, unsigned argc, jsval *vp) return false; errno = 0; CompileOptions opts(cx); - opts.setCompileAndGo(true).setNoScriptRval(true); + opts.setUTF8(true).setCompileAndGo(true).setNoScriptRval(true); if ((compileOnly && !Compile(cx, thisobj, opts, filename.ptr())) || !Evaluate(cx, thisobj, opts, filename.ptr(), NULL)) { From 624f03204cb0f6fafe35c18b0a341a2c6c69edf6 Mon Sep 17 00:00:00 2001 From: Steven Michaud Date: Wed, 7 Nov 2012 13:00:56 -0600 Subject: [PATCH 57/77] Bug 770626 - 'Back' swipe triggers several onbeforeunload popups, causes "hang". r=bgirard --- widget/cocoa/nsChildView.mm | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index e337b9f669e..43792196c62 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -3006,6 +3006,7 @@ NSEvent* gLastDragMouseDownEvent = nil; } __block BOOL animationCancelled = NO; + __block BOOL geckoSwipeEventSent = NO; // At this point, anEvent is the first scroll wheel event in a two-finger // horizontal gesture that we've decided to treat as a swipe. When we call // [NSEvent trackSwipeEventWithOptions:...], the OS interprets all @@ -3041,7 +3042,7 @@ NSEvent* gLastDragMouseDownEvent = nil; // Not waiting until isComplete is TRUE substantially reduces the // time it takes to change pages after a swipe, and helps resolve // bug 678891. - if (phase == NSEventPhaseEnded) { + if (phase == NSEventPhaseEnded && !geckoSwipeEventSent) { if (gestureAmount) { nsSimpleGestureEvent geckoEvent(true, NS_SIMPLE_GESTURE_SWIPE, mGeckoChild, 0, 0.0); [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; @@ -3050,6 +3051,17 @@ NSEvent* gLastDragMouseDownEvent = nil; } else { geckoEvent.direction |= nsIDOMSimpleGestureEvent::DIRECTION_RIGHT; } + // If DispatchWindowEvent() does something to trigger a modal dialog + // (which spins the event loop), the OS gets confused and makes + // several re-entrant calls to this handler, all of which have + // 'phase' set to NSEventPhaseEnded. Unless we do something about + // it, this results in an equal number of re-entrant calls to + // DispatchWindowEvent(), and to our modal-event handling code. + // Probably because of bug 478703, this really messes things up, + // and requires a force quit to get out of. We avoid this by + // avoiding re-entrant calls to DispatchWindowEvent(). See bug + // 770626. + geckoSwipeEventSent = YES; mGeckoChild->DispatchWindowEvent(geckoEvent); } mSwipeAnimationCancelled = nil; From 79ab1c58ea727b2717bc5a401c78430eb2bb8909 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Wed, 7 Nov 2012 14:07:35 -0500 Subject: [PATCH 58/77] Bug 802274 - Part 1: Add support for temporary autostart private browsing mode to PrivateBrowsingUtils; r=jdm --- toolkit/content/PrivateBrowsingUtils.jsm | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/toolkit/content/PrivateBrowsingUtils.jsm b/toolkit/content/PrivateBrowsingUtils.jsm index b5cbaf63316..1d524edbcd3 100644 --- a/toolkit/content/PrivateBrowsingUtils.jsm +++ b/toolkit/content/PrivateBrowsingUtils.jsm @@ -6,7 +6,11 @@ this.EXPORTED_SYMBOLS = ["PrivateBrowsingUtils"]; Components.utils.import("resource://gre/modules/Services.jsm"); -const kAutoStartPref = "browser.components.autostart"; +const kAutoStartPref = "browser.privatebrowsing.autostart"; + +// This will be set to true when the PB mode is autostarted from the command +// line for the current session. +let gTemporaryAutoStartMode = false; const Cc = Components.classes; const Ci = Components.interfaces; @@ -24,7 +28,8 @@ this.PrivateBrowsingUtils = { get permanentPrivateBrowsing() { #ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING - return Services.prefs.getBoolPref(kAutoStart, false); + return gTemporaryAutoStartMode || + Services.prefs.getBoolPref(kAutoStartPref, false); #else try { return Cc["@mozilla.org/privatebrowsing;1"]. @@ -34,12 +39,20 @@ this.PrivateBrowsingUtils = { return false; // PB not supported } #endif + }, + + // These should only be used from internal code + enterTemporaryAutoStartMode: function pbu_enterTemporaryAutoStartMode() { + gTemporaryAutoStartMode = true; + }, + get isInTemporaryAutoStartMode() { + return gTemporaryAutoStartMode; } }; #ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING function autoStartObserver(aSubject, aTopic, aData) { - var newValue = Services.prefs.getBoolPref(kAutoStart); + var newValue = Services.prefs.getBoolPref(kAutoStartPref); var windowsEnum = Services.wm.getEnumerator(null); while (windowsEnum.hasMoreElements()) { var window = windowsEnum.getNext(); From f9c594dd584abc90cfb12eaf3af2b58ec32bebb8 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Wed, 7 Nov 2012 14:03:10 -0500 Subject: [PATCH 59/77] Bug 805013 - Diagnostic bandaid to ensure that all RasterImages are removed from the discard tracker. r=joe --- image/src/RasterImage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/image/src/RasterImage.cpp b/image/src/RasterImage.cpp index 041d2405260..ef632768aa0 100644 --- a/image/src/RasterImage.cpp +++ b/image/src/RasterImage.cpp @@ -403,7 +403,6 @@ RasterImage::~RasterImage() num_discardable_containers, total_source_bytes, discardable_source_bytes)); - DiscardTracker::Remove(&mDiscardTrackerNode); } if (mDecoder) { @@ -421,6 +420,8 @@ RasterImage::~RasterImage() // Total statistics num_containers--; total_source_bytes -= mSourceData.Length(); + + DiscardTracker::Remove(&mDiscardTrackerNode); } void From 1854649bc3b22ca2037f80b839afd0756f4562ca Mon Sep 17 00:00:00 2001 From: Randell Jesup Date: Wed, 7 Nov 2012 14:07:22 -0500 Subject: [PATCH 60/77] Bug 808002: add missing consts lost in main landing for unreliable data channels r=anant --- dom/media/PeerConnection.js | 6 +++--- dom/media/bridge/IPeerConnection.idl | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js index d3622389954..6692928db5e 100644 --- a/dom/media/PeerConnection.js +++ b/dom/media/PeerConnection.js @@ -450,11 +450,11 @@ PeerConnection.prototype = { // Must determine the type where we still know if entries are undefined. let type; if (dict.maxRetransmitTime != undefined) { - type = Ci.IPeerConnection.DATACHANNEL_PARTIAL_RELIABLE_TIMED; + type = Ci.IPeerConnection.kDataChannelPartialReliableTimed; } else if (dict.maxRetransmitNum != undefined) { - type = Ci.IPeerConnection.DATACHANNEL_PARTIAL_RELIABLE_REXMIT; + type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit; } else { - type = Ci.IPeerConnection.DATACHANNEL_RELIABLE; + type = Ci.IPeerConnection.kDataChannelReliable; } // Synchronous since it doesn't block. diff --git a/dom/media/bridge/IPeerConnection.idl b/dom/media/bridge/IPeerConnection.idl index be2bc50801e..eea020c6b96 100644 --- a/dom/media/bridge/IPeerConnection.idl +++ b/dom/media/bridge/IPeerConnection.idl @@ -69,6 +69,11 @@ interface IPeerConnection : nsISupports const long kIceConnected = 3; const long kIceFailed = 4; + /* for 'type' in DataChannelInit dictionary */ + const unsigned short kDataChannelReliable = 0; + const unsigned short kDataChannelPartialReliableRexmit = 1; + const unsigned short kDataChannelPartialReliableTimed = 2; + /* Must be called first. Observer events will be dispatched on the thread provided */ void initialize(in IPeerConnectionObserver observer, in nsIDOMWindow window, [optional] in nsIThread thread); From ca2039bc85835aba05451609d78953bfe5fdc85c Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Wed, 7 Nov 2012 14:14:27 -0500 Subject: [PATCH 61/77] Bug 800106 - Drop inline annotations in nsUTF8Utils.h; r=dbaron --- xpcom/string/public/nsUTF8Utils.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xpcom/string/public/nsUTF8Utils.h b/xpcom/string/public/nsUTF8Utils.h index 1e4a0a1bda7..be9ea17582f 100644 --- a/xpcom/string/public/nsUTF8Utils.h +++ b/xpcom/string/public/nsUTF8Utils.h @@ -286,7 +286,7 @@ class ConvertUTF8toUTF16 bool ErrorEncountered() const { return mErrorEncountered; } - void NS_ALWAYS_INLINE write( const value_type* start, uint32_t N ) + void write( const value_type* start, uint32_t N ) { if ( mErrorEncountered ) return; @@ -345,7 +345,7 @@ class CalculateUTF8Length size_t Length() const { return mLength; } - void NS_ALWAYS_INLINE write( const value_type* start, uint32_t N ) + void write( const value_type* start, uint32_t N ) { // ignore any further requests if ( mErrorEncountered ) @@ -449,7 +449,7 @@ class ConvertUTF16toUTF8 size_t Size() const { return mBuffer - mStart; } - void NS_ALWAYS_INLINE write( const value_type* start, uint32_t N ) + void write( const value_type* start, uint32_t N ) { buffer_type *out = mBuffer; // gcc isn't smart enough to do this! @@ -566,7 +566,7 @@ class CalculateUTF8Size size_t Size() const { return mSize; } - void NS_ALWAYS_INLINE write( const value_type* start, uint32_t N ) + void write( const value_type* start, uint32_t N ) { // Assume UCS2 surrogate pairs won't be spread across fragments. for (const value_type *p = start, *end = start + N; p < end; ++p ) From 513125c2363c68bd32b8d79cddeaa9b3f9c0fa7d Mon Sep 17 00:00:00 2001 From: Jacek Szpot Date: Sat, 13 Oct 2012 00:36:39 +0200 Subject: [PATCH 62/77] Bug 800106: Remove NS_ALWAYS_INLINE from configure.in; r=ehsan --- configure.in | 6 ------ js/src/configure.in | 6 ------ 2 files changed, 12 deletions(-) diff --git a/configure.in b/configure.in index 763535e21cb..915bb36374b 100644 --- a/configure.in +++ b/configure.in @@ -3832,12 +3832,6 @@ dnl are defined in build/autoconf/altoptions.m4. dnl If the compiler supports these attributes, define them as dnl convenience macros. -if test "$ac_cv_attribute_always_inline" = yes ; then - AC_DEFINE(NS_ALWAYS_INLINE, [__attribute__((always_inline))]) -else - AC_DEFINE(NS_ALWAYS_INLINE,) -fi - if test "$ac_cv_attribute_malloc" = yes ; then AC_DEFINE(NS_ATTR_MALLOC, [__attribute__((malloc))]) else diff --git a/js/src/configure.in b/js/src/configure.in index e25f5695d6d..27068db11df 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -3136,12 +3136,6 @@ dnl are defined in build/autoconf/altoptions.m4. dnl If the compiler supports these attributes, define them as dnl convenience macros. -if test "$ac_cv_attribute_always_inline" = yes ; then - AC_DEFINE(NS_ALWAYS_INLINE, [__attribute__((always_inline))]) -else - AC_DEFINE(NS_ALWAYS_INLINE,) -fi - if test "$ac_cv_attribute_malloc" = yes ; then AC_DEFINE(NS_ATTR_MALLOC, [__attribute__((malloc))]) else From 4379870e44e00e86308b989fce97d8156e54e723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Lo=CC=81pez?= Date: Wed, 31 Oct 2012 04:58:00 -0700 Subject: [PATCH 63/77] Bug 807276. Make nsIWebSocketChannel.idl scriptable r=jduell, sr=bz DONTBUILD --- netwerk/protocol/websocket/nsIWebSocketChannel.idl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/netwerk/protocol/websocket/nsIWebSocketChannel.idl b/netwerk/protocol/websocket/nsIWebSocketChannel.idl index 3697bea206e..054751fae68 100644 --- a/netwerk/protocol/websocket/nsIWebSocketChannel.idl +++ b/netwerk/protocol/websocket/nsIWebSocketChannel.idl @@ -12,10 +12,14 @@ interface nsIInputStream; #include "nsISupports.idl" -/** - * You probably want nsI{Moz}WebSocket.idl +** + * Low-level websocket API: handles network protocol. + * + * This is primarly intended for use by the higher-level nsIWebSocket.idl. + * We are also making it scriptable for now, but this may change once we have + * WebSockets for Workers. */ -[uuid(0683E9A4-994D-11E1-9478-1E356188709B)] +[scriptable, uuid(0683E9A4-994D-11E1-9478-1E356188709B)] interface nsIWebSocketChannel : nsISupports { /** From 36c0583c7c016ad86f5055b39ae24e62d8de4685 Mon Sep 17 00:00:00 2001 From: Jim Mathies Date: Wed, 7 Nov 2012 13:27:00 -0600 Subject: [PATCH 64/77] Bug 809158 - Cleanup enable-metro configure.in sections and set the proper WINVER for js in metro builds. r=bbondy --- configure.in | 1 - js/src/configure.in | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/configure.in b/configure.in index 915bb36374b..c7eda85cc86 100644 --- a/configure.in +++ b/configure.in @@ -402,7 +402,6 @@ dnl ======================================================== dnl Special win32 checks dnl ======================================================== -# With win8, sdk target=602, WINVER=602 MOZ_ARG_ENABLE_BOOL(metro, [ --enable-metro Enable Windows Metro build targets], MOZ_METRO=1, diff --git a/js/src/configure.in b/js/src/configure.in index 27068db11df..d0f1d0dcd74 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -346,7 +346,6 @@ MOZ_TOOL_VARIABLES dnl Special win32 checks dnl ======================================================== -# With win8, sdk target=602, WINVER=602 MOZ_ARG_ENABLE_BOOL(metro, [ --enable-metro Enable Windows Metro build targets], MOZ_METRO=1, @@ -355,8 +354,7 @@ if test -n "$MOZ_METRO"; then AC_DEFINE(MOZ_METRO) # Target the Windows 8 Kit WINSDK_TARGETVER=602 - # Allow a higher api set - WINVER=602 + WINVER=502 else # Target the Windows 7 SDK by default WINSDK_TARGETVER=601 From bf91bdf97dc807a4c553e5da36d5356d10e164dc Mon Sep 17 00:00:00 2001 From: Daniel Holbert Date: Wed, 7 Nov 2012 11:28:59 -0800 Subject: [PATCH 65/77] Bug 783415 part 1: Make "display" value of flex items compute to their blockified forms. r=dbaron --- layout/base/nsCSSFrameConstructor.cpp | 3 ++ .../flexbox-align-self-baseline-horiz-1.xhtml | 6 --- .../flexbox-align-self-baseline-horiz-3.xhtml | 6 --- .../reftests/flexbox/flexbox-float-2a.xhtml | 8 ++-- .../flexbox-inlinecontent-horiz-3-ref.xhtml | 2 +- .../flexbox-inlinecontent-horiz-3a.xhtml | 4 +- .../flexbox-inlinecontent-horiz-3b.xhtml | 5 +- .../flexbox-inlinecontent-horiz-3c.xhtml | 5 +- .../flexbox-inlinecontent-horiz-5.xhtml | 4 +- .../flexbox-inlinecontent-horiz-6-ref.xhtml | 39 ---------------- .../flexbox-inlinecontent-horiz-6.xhtml | 40 ---------------- .../flexbox-whitespace-handling-3-ref.xhtml | 32 ------------- .../flexbox-whitespace-handling-3.xhtml | 36 --------------- .../flexbox-widget-flex-items-1-ref.html | 1 + .../flexbox-widget-flex-items-2-ref.html | 2 + .../flexbox-widget-flex-items-3-ref.html | 1 + .../flexbox-widget-flex-items-4-ref.html | 1 + layout/reftests/flexbox/reftest.list | 14 ++---- layout/style/nsRuleNode.cpp | 4 +- layout/style/nsRuleNode.h | 1 + layout/style/nsStyleContext.cpp | 46 +++++++++++++++++++ 21 files changed, 76 insertions(+), 184 deletions(-) delete mode 100644 layout/reftests/flexbox/flexbox-inlinecontent-horiz-6-ref.xhtml delete mode 100644 layout/reftests/flexbox/flexbox-inlinecontent-horiz-6.xhtml delete mode 100644 layout/reftests/flexbox/flexbox-whitespace-handling-3-ref.xhtml delete mode 100644 layout/reftests/flexbox/flexbox-whitespace-handling-3.xhtml diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index ecd036628e3..06cf4f38d62 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -4408,6 +4408,9 @@ nsCSSFrameConstructor::FindDisplayData(const nsStyleDisplay* aDisplay, FULL_CTOR_FCDATA(0, &nsCSSFrameConstructor::ConstructTable) }, { NS_STYLE_DISPLAY_INLINE_TABLE, FULL_CTOR_FCDATA(0, &nsCSSFrameConstructor::ConstructTable) }, + // NOTE: In the unlikely event that we add another table-part here that has + // a desired-parent-type (& hence triggers table fixup), we'll need to also + // update the flexbox chunk in nsStyleContext::ApplyStyleFixups(). { NS_STYLE_DISPLAY_TABLE_CAPTION, FCDATA_DECL(FCDATA_IS_TABLE_PART | FCDATA_ALLOW_BLOCK_STYLES | FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_SKIP_ABSPOS_PUSH | diff --git a/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-1.xhtml b/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-1.xhtml index 3de9065a819..10746a4f2bb 100644 --- a/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-1.xhtml +++ b/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-1.xhtml @@ -18,12 +18,6 @@ font: 14px sans-serif; } - i { - /* XXXdholbert HACK - REMOVEME after bug 783415 lands (which will - make this display:block conversion happen automatically). */ - display: block; - } - .big { height: 100px; font: 24px sans-serif; diff --git a/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-3.xhtml b/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-3.xhtml index 9b41e0da6e9..199162379a9 100644 --- a/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-3.xhtml +++ b/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-3.xhtml @@ -21,12 +21,6 @@ font: 14px sans-serif; } - input, label { - /* XXXdholbert HACK - REMOVEME after bug 783415 lands (which will - make this display:block conversion happen automatically). */ - display: block; - } - .big { height: 100px; font: 24px sans-serif; diff --git a/layout/reftests/flexbox/flexbox-float-2a.xhtml b/layout/reftests/flexbox/flexbox-float-2a.xhtml index 04d1fe124b2..817e3fb2c4f 100644 --- a/layout/reftests/flexbox/flexbox-float-2a.xhtml +++ b/layout/reftests/flexbox/flexbox-float-2a.xhtml @@ -24,19 +24,19 @@
    - aaalllbbb + aaalllbbb
    - aaarrrbbb + aaarrrbbb
    - aaa
    lll
    bbb + aaa
    lll
    bbb
    - aaa
    rrr
    bbb + aaa
    rrr
    bbb
    diff --git a/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3-ref.xhtml b/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3-ref.xhtml index d2b45453917..4c28d8d206d 100644 --- a/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3-ref.xhtml +++ b/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3-ref.xhtml @@ -17,6 +17,6 @@ -
    abc def ghi jkl mno pqr stu
    +
    abc def ghi jkl mno pqr stu
    diff --git a/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3a.xhtml b/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3a.xhtml index 8eb3ef84316..2b88ce2ae28 100644 --- a/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3a.xhtml +++ b/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3a.xhtml @@ -4,7 +4,7 @@ http://creativecommons.org/publicdomain/zero/1.0/ --> @@ -19,6 +19,6 @@ -
    abc def ghi jkl mno pqr stu
    +
    abc def ghi jkl mno pqr stu
    diff --git a/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3b.xhtml b/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3b.xhtml index 65d1dedc4e7..b5d509f18ab 100644 --- a/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3b.xhtml +++ b/layout/reftests/flexbox/flexbox-inlinecontent-horiz-3b.xhtml @@ -14,8 +14,7 @@ + + + diff --git a/layout/style/test/test_flexbox_child_display_values.html b/layout/style/test/test_flexbox_child_display_values.html new file mode 100644 index 00000000000..d9f46962571 --- /dev/null +++ b/layout/style/test/test_flexbox_child_display_values.html @@ -0,0 +1,47 @@ + + + + + + Test "display" values of content in a flex container (Bug 783415) + + + + +Mozilla Bug 783415 +
    + +
    +
    +
    +
    + + From 81b21d68c445365daaa6990ecf002494b281300e Mon Sep 17 00:00:00 2001 From: Jason Duell Date: Wed, 7 Nov 2012 11:43:38 -0800 Subject: [PATCH 67/77] Backed out changeset f7753f215359 --- netwerk/protocol/websocket/nsIWebSocketChannel.idl | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/netwerk/protocol/websocket/nsIWebSocketChannel.idl b/netwerk/protocol/websocket/nsIWebSocketChannel.idl index 054751fae68..3697bea206e 100644 --- a/netwerk/protocol/websocket/nsIWebSocketChannel.idl +++ b/netwerk/protocol/websocket/nsIWebSocketChannel.idl @@ -12,14 +12,10 @@ interface nsIInputStream; #include "nsISupports.idl" -** - * Low-level websocket API: handles network protocol. - * - * This is primarly intended for use by the higher-level nsIWebSocket.idl. - * We are also making it scriptable for now, but this may change once we have - * WebSockets for Workers. +/** + * You probably want nsI{Moz}WebSocket.idl */ -[scriptable, uuid(0683E9A4-994D-11E1-9478-1E356188709B)] +[uuid(0683E9A4-994D-11E1-9478-1E356188709B)] interface nsIWebSocketChannel : nsISupports { /** From 30e96e220f6d00a1f8b33be375550ae8f1cd9bb9 Mon Sep 17 00:00:00 2001 From: Ted Mielczarek Date: Mon, 29 Oct 2012 11:12:30 -0400 Subject: [PATCH 68/77] bug 803654 - Import mock into virtualenv. r=jhammel --- build/virtualenv/packages.txt | 1 + python/mock-1.0.0/LICENSE.txt | 26 + python/mock-1.0.0/MANIFEST.in | 2 + python/mock-1.0.0/PKG-INFO | 208 ++ python/mock-1.0.0/README.txt | 177 ++ python/mock-1.0.0/docs/changelog.txt | 725 +++++ python/mock-1.0.0/docs/compare.txt | 628 +++++ python/mock-1.0.0/docs/conf.py | 209 ++ python/mock-1.0.0/docs/examples.txt | 1063 ++++++++ python/mock-1.0.0/docs/getting-started.txt | 479 ++++ python/mock-1.0.0/docs/helpers.txt | 583 ++++ python/mock-1.0.0/docs/index.txt | 411 +++ python/mock-1.0.0/docs/magicmock.txt | 258 ++ python/mock-1.0.0/docs/mock.txt | 842 ++++++ python/mock-1.0.0/docs/patch.txt | 636 +++++ python/mock-1.0.0/docs/sentinel.txt | 58 + .../html/.doctrees/changelog.doctree | Bin 0 -> 282659 bytes .../mock-1.0.0/html/.doctrees/compare.doctree | Bin 0 -> 56915 bytes .../html/.doctrees/examples.doctree | Bin 0 -> 167478 bytes .../html/.doctrees/getting-started.doctree | Bin 0 -> 70942 bytes .../mock-1.0.0/html/.doctrees/index.doctree | Bin 0 -> 98784 bytes .../html/.doctrees/magicmock.doctree | Bin 0 -> 75713 bytes python/mock-1.0.0/html/.doctrees/mock.doctree | Bin 0 -> 152111 bytes .../html/.doctrees/mocksignature.doctree | Bin 0 -> 42324 bytes .../mock-1.0.0/html/.doctrees/patch.doctree | Bin 0 -> 123511 bytes .../html/.doctrees/sentinel.doctree | Bin 0 -> 10632 bytes python/mock-1.0.0/html/_sources/changelog.txt | 725 +++++ python/mock-1.0.0/html/_sources/compare.txt | 628 +++++ python/mock-1.0.0/html/_sources/examples.txt | 1063 ++++++++ .../html/_sources/getting-started.txt | 479 ++++ python/mock-1.0.0/html/_sources/index.txt | 411 +++ python/mock-1.0.0/html/_sources/magicmock.txt | 258 ++ python/mock-1.0.0/html/_sources/mock.txt | 842 ++++++ .../html/_sources/mocksignature.txt | 262 ++ python/mock-1.0.0/html/_sources/patch.txt | 636 +++++ python/mock-1.0.0/html/_sources/sentinel.txt | 58 + python/mock-1.0.0/html/_static/adctheme.css | 757 ++++++ python/mock-1.0.0/html/_static/basic.css | 540 ++++ .../html/_static/breadcrumb_background.png | Bin 0 -> 136 bytes python/mock-1.0.0/html/_static/default.css | 256 ++ python/mock-1.0.0/html/_static/doctools.js | 247 ++ .../mock-1.0.0/html/_static/documentation.png | Bin 0 -> 412 bytes python/mock-1.0.0/html/_static/file.png | Bin 0 -> 392 bytes .../mock-1.0.0/html/_static/header_sm_mid.png | Bin 0 -> 159 bytes python/mock-1.0.0/html/_static/jquery.js | 154 ++ python/mock-1.0.0/html/_static/minus.png | Bin 0 -> 199 bytes python/mock-1.0.0/html/_static/mobile.css | 17 + python/mock-1.0.0/html/_static/plus.png | Bin 0 -> 199 bytes python/mock-1.0.0/html/_static/pygments.css | 62 + python/mock-1.0.0/html/_static/scrn1.png | Bin 0 -> 108046 bytes python/mock-1.0.0/html/_static/scrn2.png | Bin 0 -> 121395 bytes .../html/_static/searchfield_leftcap.png | Bin 0 -> 855 bytes .../html/_static/searchfield_repeat.png | Bin 0 -> 158 bytes .../html/_static/searchfield_rightcap.png | Bin 0 -> 530 bytes python/mock-1.0.0/html/_static/searchtools.js | 560 ++++ python/mock-1.0.0/html/_static/sidebar.js | 148 ++ .../html/_static/title_background.png | Bin 0 -> 132 bytes python/mock-1.0.0/html/_static/toc.js | 20 + .../html/_static/triangle_closed.png | Bin 0 -> 181 bytes .../mock-1.0.0/html/_static/triangle_left.png | Bin 0 -> 195 bytes .../mock-1.0.0/html/_static/triangle_open.png | Bin 0 -> 191 bytes python/mock-1.0.0/html/_static/underscore.js | 23 + python/mock-1.0.0/html/changelog.html | 839 ++++++ python/mock-1.0.0/html/compare.html | 672 +++++ python/mock-1.0.0/html/examples.html | 1006 +++++++ python/mock-1.0.0/html/genindex.html | 479 ++++ python/mock-1.0.0/html/getting-started.html | 510 ++++ python/mock-1.0.0/html/index.html | 529 ++++ python/mock-1.0.0/html/magicmock.html | 347 +++ python/mock-1.0.0/html/mock.html | 875 ++++++ python/mock-1.0.0/html/mocksignature.html | 352 +++ python/mock-1.0.0/html/objects.inv | Bin 0 -> 711 bytes python/mock-1.0.0/html/output.txt | 126 + python/mock-1.0.0/html/patch.html | 648 +++++ python/mock-1.0.0/html/search.html | 99 + python/mock-1.0.0/html/searchindex.js | 1 + python/mock-1.0.0/html/sentinel.html | 156 ++ python/mock-1.0.0/mock.egg-info/PKG-INFO | 208 ++ python/mock-1.0.0/mock.egg-info/SOURCES.txt | 94 + .../mock.egg-info/dependency_links.txt | 1 + python/mock-1.0.0/mock.egg-info/top_level.txt | 1 + python/mock-1.0.0/mock.py | 2356 +++++++++++++++++ python/mock-1.0.0/setup.cfg | 12 + python/mock-1.0.0/setup.py | 72 + python/mock-1.0.0/tests/__init__.py | 3 + python/mock-1.0.0/tests/_testwith.py | 181 ++ python/mock-1.0.0/tests/support.py | 41 + python/mock-1.0.0/tests/support_with.py | 93 + python/mock-1.0.0/tests/testcallable.py | 158 ++ python/mock-1.0.0/tests/testhelpers.py | 940 +++++++ python/mock-1.0.0/tests/testmagicmethods.py | 486 ++++ python/mock-1.0.0/tests/testmock.py | 1351 ++++++++++ python/mock-1.0.0/tests/testpatch.py | 1790 +++++++++++++ python/mock-1.0.0/tests/testsentinel.py | 33 + python/mock-1.0.0/tests/testwith.py | 16 + python/mock-1.0.0/tox.ini | 40 + 96 files changed, 28967 insertions(+) create mode 100644 python/mock-1.0.0/LICENSE.txt create mode 100644 python/mock-1.0.0/MANIFEST.in create mode 100644 python/mock-1.0.0/PKG-INFO create mode 100644 python/mock-1.0.0/README.txt create mode 100644 python/mock-1.0.0/docs/changelog.txt create mode 100644 python/mock-1.0.0/docs/compare.txt create mode 100644 python/mock-1.0.0/docs/conf.py create mode 100644 python/mock-1.0.0/docs/examples.txt create mode 100644 python/mock-1.0.0/docs/getting-started.txt create mode 100644 python/mock-1.0.0/docs/helpers.txt create mode 100644 python/mock-1.0.0/docs/index.txt create mode 100644 python/mock-1.0.0/docs/magicmock.txt create mode 100644 python/mock-1.0.0/docs/mock.txt create mode 100644 python/mock-1.0.0/docs/patch.txt create mode 100644 python/mock-1.0.0/docs/sentinel.txt create mode 100644 python/mock-1.0.0/html/.doctrees/changelog.doctree create mode 100644 python/mock-1.0.0/html/.doctrees/compare.doctree create mode 100644 python/mock-1.0.0/html/.doctrees/examples.doctree create mode 100644 python/mock-1.0.0/html/.doctrees/getting-started.doctree create mode 100644 python/mock-1.0.0/html/.doctrees/index.doctree create mode 100644 python/mock-1.0.0/html/.doctrees/magicmock.doctree create mode 100644 python/mock-1.0.0/html/.doctrees/mock.doctree create mode 100644 python/mock-1.0.0/html/.doctrees/mocksignature.doctree create mode 100644 python/mock-1.0.0/html/.doctrees/patch.doctree create mode 100644 python/mock-1.0.0/html/.doctrees/sentinel.doctree create mode 100644 python/mock-1.0.0/html/_sources/changelog.txt create mode 100644 python/mock-1.0.0/html/_sources/compare.txt create mode 100644 python/mock-1.0.0/html/_sources/examples.txt create mode 100644 python/mock-1.0.0/html/_sources/getting-started.txt create mode 100644 python/mock-1.0.0/html/_sources/index.txt create mode 100644 python/mock-1.0.0/html/_sources/magicmock.txt create mode 100644 python/mock-1.0.0/html/_sources/mock.txt create mode 100644 python/mock-1.0.0/html/_sources/mocksignature.txt create mode 100644 python/mock-1.0.0/html/_sources/patch.txt create mode 100644 python/mock-1.0.0/html/_sources/sentinel.txt create mode 100644 python/mock-1.0.0/html/_static/adctheme.css create mode 100644 python/mock-1.0.0/html/_static/basic.css create mode 100644 python/mock-1.0.0/html/_static/breadcrumb_background.png create mode 100644 python/mock-1.0.0/html/_static/default.css create mode 100644 python/mock-1.0.0/html/_static/doctools.js create mode 100644 python/mock-1.0.0/html/_static/documentation.png create mode 100644 python/mock-1.0.0/html/_static/file.png create mode 100644 python/mock-1.0.0/html/_static/header_sm_mid.png create mode 100644 python/mock-1.0.0/html/_static/jquery.js create mode 100644 python/mock-1.0.0/html/_static/minus.png create mode 100644 python/mock-1.0.0/html/_static/mobile.css create mode 100644 python/mock-1.0.0/html/_static/plus.png create mode 100644 python/mock-1.0.0/html/_static/pygments.css create mode 100644 python/mock-1.0.0/html/_static/scrn1.png create mode 100644 python/mock-1.0.0/html/_static/scrn2.png create mode 100644 python/mock-1.0.0/html/_static/searchfield_leftcap.png create mode 100644 python/mock-1.0.0/html/_static/searchfield_repeat.png create mode 100644 python/mock-1.0.0/html/_static/searchfield_rightcap.png create mode 100644 python/mock-1.0.0/html/_static/searchtools.js create mode 100644 python/mock-1.0.0/html/_static/sidebar.js create mode 100644 python/mock-1.0.0/html/_static/title_background.png create mode 100644 python/mock-1.0.0/html/_static/toc.js create mode 100644 python/mock-1.0.0/html/_static/triangle_closed.png create mode 100644 python/mock-1.0.0/html/_static/triangle_left.png create mode 100644 python/mock-1.0.0/html/_static/triangle_open.png create mode 100644 python/mock-1.0.0/html/_static/underscore.js create mode 100644 python/mock-1.0.0/html/changelog.html create mode 100644 python/mock-1.0.0/html/compare.html create mode 100644 python/mock-1.0.0/html/examples.html create mode 100644 python/mock-1.0.0/html/genindex.html create mode 100644 python/mock-1.0.0/html/getting-started.html create mode 100644 python/mock-1.0.0/html/index.html create mode 100644 python/mock-1.0.0/html/magicmock.html create mode 100644 python/mock-1.0.0/html/mock.html create mode 100644 python/mock-1.0.0/html/mocksignature.html create mode 100644 python/mock-1.0.0/html/objects.inv create mode 100644 python/mock-1.0.0/html/output.txt create mode 100644 python/mock-1.0.0/html/patch.html create mode 100644 python/mock-1.0.0/html/search.html create mode 100644 python/mock-1.0.0/html/searchindex.js create mode 100644 python/mock-1.0.0/html/sentinel.html create mode 100644 python/mock-1.0.0/mock.egg-info/PKG-INFO create mode 100644 python/mock-1.0.0/mock.egg-info/SOURCES.txt create mode 100644 python/mock-1.0.0/mock.egg-info/dependency_links.txt create mode 100644 python/mock-1.0.0/mock.egg-info/top_level.txt create mode 100644 python/mock-1.0.0/mock.py create mode 100644 python/mock-1.0.0/setup.cfg create mode 100755 python/mock-1.0.0/setup.py create mode 100644 python/mock-1.0.0/tests/__init__.py create mode 100644 python/mock-1.0.0/tests/_testwith.py create mode 100644 python/mock-1.0.0/tests/support.py create mode 100644 python/mock-1.0.0/tests/support_with.py create mode 100644 python/mock-1.0.0/tests/testcallable.py create mode 100644 python/mock-1.0.0/tests/testhelpers.py create mode 100644 python/mock-1.0.0/tests/testmagicmethods.py create mode 100644 python/mock-1.0.0/tests/testmock.py create mode 100644 python/mock-1.0.0/tests/testpatch.py create mode 100644 python/mock-1.0.0/tests/testsentinel.py create mode 100644 python/mock-1.0.0/tests/testwith.py create mode 100644 python/mock-1.0.0/tox.ini diff --git a/build/virtualenv/packages.txt b/build/virtualenv/packages.txt index 3afd61dc535..13c1b8841e9 100644 --- a/build/virtualenv/packages.txt +++ b/build/virtualenv/packages.txt @@ -17,6 +17,7 @@ pymake.pth:build/pymake optional:setup.py:python/psutil:build_ext:--inplace optional:psutil.pth:python/psutil which.pth:python/which +mock.pth:python/mock-1.0.0 mozilla.pth:build mozilla.pth:config copy:build/buildconfig.py diff --git a/python/mock-1.0.0/LICENSE.txt b/python/mock-1.0.0/LICENSE.txt new file mode 100644 index 00000000000..7891703b134 --- /dev/null +++ b/python/mock-1.0.0/LICENSE.txt @@ -0,0 +1,26 @@ +Copyright (c) 2003-2012, Michael Foord +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/python/mock-1.0.0/MANIFEST.in b/python/mock-1.0.0/MANIFEST.in new file mode 100644 index 00000000000..d52b301de65 --- /dev/null +++ b/python/mock-1.0.0/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE.txt tox.ini tests/*.py +recursive-include docs *.txt *.py *.png *.css *.html *.js diff --git a/python/mock-1.0.0/PKG-INFO b/python/mock-1.0.0/PKG-INFO new file mode 100644 index 00000000000..4c7309c713c --- /dev/null +++ b/python/mock-1.0.0/PKG-INFO @@ -0,0 +1,208 @@ +Metadata-Version: 1.0 +Name: mock +Version: 1.0.0 +Summary: A Python Mocking and Patching Library for Testing +Home-page: http://www.voidspace.org.uk/python/mock/ +Author: Michael Foord +Author-email: michael@voidspace.org.uk +License: UNKNOWN +Description: mock is a library for testing in Python. It allows you to replace parts of + your system under test with mock objects and make assertions about how they + have been used. + + mock is now part of the Python standard library, available as `unittest.mock < + http://docs.python.org/py3k/library/unittest.mock.html#module-unittest.mock>`_ + in Python 3.3 onwards. + + mock provides a core `MagicMock` class removing the need to create a host of + stubs throughout your test suite. After performing an action, you can make + assertions about which methods / attributes were used and arguments they were + called with. You can also specify return values and set needed attributes in + the normal way. + + mock is tested on Python versions 2.4-2.7 and Python 3. mock is also tested + with the latest versions of Jython and pypy. + + The mock module also provides utility functions / objects to assist with + testing, particularly monkey patching. + + * `PDF documentation for 1.0 beta 1 + `_ + * `mock on google code (repository and issue tracker) + `_ + * `mock documentation + `_ + * `mock on PyPI `_ + * `Mailing list (testing-in-python@lists.idyll.org) + `_ + + Mock is very easy to use and is designed for use with + `unittest `_. Mock is based on + the 'action -> assertion' pattern instead of 'record -> replay' used by many + mocking frameworks. See the `mock documentation`_ for full details. + + Mock objects create all attributes and methods as you access them and store + details of how they have been used. You can configure them, to specify return + values or limit what attributes are available, and then make assertions about + how they have been used:: + + >>> from mock import Mock + >>> real = ProductionClass() + >>> real.method = Mock(return_value=3) + >>> real.method(3, 4, 5, key='value') + 3 + >>> real.method.assert_called_with(3, 4, 5, key='value') + + `side_effect` allows you to perform side effects, return different values or + raise an exception when a mock is called:: + + >>> mock = Mock(side_effect=KeyError('foo')) + >>> mock() + Traceback (most recent call last): + ... + KeyError: 'foo' + >>> values = {'a': 1, 'b': 2, 'c': 3} + >>> def side_effect(arg): + ... return values[arg] + ... + >>> mock.side_effect = side_effect + >>> mock('a'), mock('b'), mock('c') + (3, 2, 1) + >>> mock.side_effect = [5, 4, 3, 2, 1] + >>> mock(), mock(), mock() + (5, 4, 3) + + Mock has many other ways you can configure it and control its behaviour. For + example the `spec` argument configures the mock to take its specification from + another object. Attempting to access attributes or methods on the mock that + don't exist on the spec will fail with an `AttributeError`. + + The `patch` decorator / context manager makes it easy to mock classes or + objects in a module under test. The object you specify will be replaced with a + mock (or other object) during the test and restored when the test ends:: + + >>> from mock import patch + >>> @patch('test_module.ClassName1') + ... @patch('test_module.ClassName2') + ... def test(MockClass2, MockClass1): + ... test_module.ClassName1() + ... test_module.ClassName2() + + ... assert MockClass1.called + ... assert MockClass2.called + ... + >>> test() + + .. note:: + + When you nest patch decorators the mocks are passed in to the decorated + function in the same order they applied (the normal *python* order that + decorators are applied). This means from the bottom up, so in the example + above the mock for `test_module.ClassName2` is passed in first. + + With `patch` it matters that you patch objects in the namespace where they + are looked up. This is normally straightforward, but for a quick guide + read `where to patch + `_. + + As well as a decorator `patch` can be used as a context manager in a with + statement:: + + >>> with patch.object(ProductionClass, 'method') as mock_method: + ... mock_method.return_value = None + ... real = ProductionClass() + ... real.method(1, 2, 3) + ... + >>> mock_method.assert_called_once_with(1, 2, 3) + + There is also `patch.dict` for setting values in a dictionary just during the + scope of a test and restoring the dictionary to its original state when the + test ends:: + + >>> foo = {'key': 'value'} + >>> original = foo.copy() + >>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True): + ... assert foo == {'newkey': 'newvalue'} + ... + >>> assert foo == original + + Mock supports the mocking of Python magic methods. The easiest way of + using magic methods is with the `MagicMock` class. It allows you to do + things like:: + + >>> from mock import MagicMock + >>> mock = MagicMock() + >>> mock.__str__.return_value = 'foobarbaz' + >>> str(mock) + 'foobarbaz' + >>> mock.__str__.assert_called_once_with() + + Mock allows you to assign functions (or other Mock instances) to magic methods + and they will be called appropriately. The MagicMock class is just a Mock + variant that has all of the magic methods pre-created for you (well - all the + useful ones anyway). + + The following is an example of using magic methods with the ordinary Mock + class:: + + >>> from mock import Mock + >>> mock = Mock() + >>> mock.__str__ = Mock(return_value = 'wheeeeee') + >>> str(mock) + 'wheeeeee' + + For ensuring that the mock objects your tests use have the same api as the + objects they are replacing, you can use "auto-speccing". Auto-speccing can + be done through the `autospec` argument to patch, or the `create_autospec` + function. Auto-speccing creates mock objects that have the same attributes + and methods as the objects they are replacing, and any functions and methods + (including constructors) have the same call signature as the real object. + + This ensures that your mocks will fail in the same way as your production + code if they are used incorrectly:: + + >>> from mock import create_autospec + >>> def function(a, b, c): + ... pass + ... + >>> mock_function = create_autospec(function, return_value='fishy') + >>> mock_function(1, 2, 3) + 'fishy' + >>> mock_function.assert_called_once_with(1, 2, 3) + >>> mock_function('wrong arguments') + Traceback (most recent call last): + ... + TypeError: () takes exactly 3 arguments (1 given) + + `create_autospec` can also be used on classes, where it copies the signature of + the `__init__` method, and on callable objects where it copies the signature of + the `__call__` method. + + The distribution contains tests and documentation. The tests require + `unittest2 `_ to run. + + Docs from the in-development version of `mock` can be found at + `mock.readthedocs.org `_. + +Keywords: testing,test,mock,mocking,unittest,patching,stubs,fakes,doubles +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 2.4 +Classifier: Programming Language :: Python :: 2.5 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.1 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: Implementation :: Jython +Classifier: Operating System :: OS Independent +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Software Development :: Testing diff --git a/python/mock-1.0.0/README.txt b/python/mock-1.0.0/README.txt new file mode 100644 index 00000000000..385db3cae62 --- /dev/null +++ b/python/mock-1.0.0/README.txt @@ -0,0 +1,177 @@ +mock is a library for testing in Python. It allows you to replace parts of +your system under test with mock objects and make assertions about how they +have been used. + +mock is now part of the Python standard library, available as `unittest.mock < +http://docs.python.org/py3k/library/unittest.mock.html#module-unittest.mock>`_ +in Python 3.3 onwards. + +mock provides a core `MagicMock` class removing the need to create a host of +stubs throughout your test suite. After performing an action, you can make +assertions about which methods / attributes were used and arguments they were +called with. You can also specify return values and set needed attributes in +the normal way. + +mock is tested on Python versions 2.4-2.7 and Python 3. mock is also tested +with the latest versions of Jython and pypy. + +The mock module also provides utility functions / objects to assist with +testing, particularly monkey patching. + +* `PDF documentation for 1.0 beta 1 + `_ +* `mock on google code (repository and issue tracker) + `_ +* `mock documentation + `_ +* `mock on PyPI `_ +* `Mailing list (testing-in-python@lists.idyll.org) + `_ + +Mock is very easy to use and is designed for use with +`unittest `_. Mock is based on +the 'action -> assertion' pattern instead of 'record -> replay' used by many +mocking frameworks. See the `mock documentation`_ for full details. + +Mock objects create all attributes and methods as you access them and store +details of how they have been used. You can configure them, to specify return +values or limit what attributes are available, and then make assertions about +how they have been used:: + + >>> from mock import Mock + >>> real = ProductionClass() + >>> real.method = Mock(return_value=3) + >>> real.method(3, 4, 5, key='value') + 3 + >>> real.method.assert_called_with(3, 4, 5, key='value') + +`side_effect` allows you to perform side effects, return different values or +raise an exception when a mock is called:: + + >>> mock = Mock(side_effect=KeyError('foo')) + >>> mock() + Traceback (most recent call last): + ... + KeyError: 'foo' + >>> values = {'a': 1, 'b': 2, 'c': 3} + >>> def side_effect(arg): + ... return values[arg] + ... + >>> mock.side_effect = side_effect + >>> mock('a'), mock('b'), mock('c') + (3, 2, 1) + >>> mock.side_effect = [5, 4, 3, 2, 1] + >>> mock(), mock(), mock() + (5, 4, 3) + +Mock has many other ways you can configure it and control its behaviour. For +example the `spec` argument configures the mock to take its specification from +another object. Attempting to access attributes or methods on the mock that +don't exist on the spec will fail with an `AttributeError`. + +The `patch` decorator / context manager makes it easy to mock classes or +objects in a module under test. The object you specify will be replaced with a +mock (or other object) during the test and restored when the test ends:: + + >>> from mock import patch + >>> @patch('test_module.ClassName1') + ... @patch('test_module.ClassName2') + ... def test(MockClass2, MockClass1): + ... test_module.ClassName1() + ... test_module.ClassName2() + + ... assert MockClass1.called + ... assert MockClass2.called + ... + >>> test() + +.. note:: + + When you nest patch decorators the mocks are passed in to the decorated + function in the same order they applied (the normal *python* order that + decorators are applied). This means from the bottom up, so in the example + above the mock for `test_module.ClassName2` is passed in first. + + With `patch` it matters that you patch objects in the namespace where they + are looked up. This is normally straightforward, but for a quick guide + read `where to patch + `_. + +As well as a decorator `patch` can be used as a context manager in a with +statement:: + + >>> with patch.object(ProductionClass, 'method') as mock_method: + ... mock_method.return_value = None + ... real = ProductionClass() + ... real.method(1, 2, 3) + ... + >>> mock_method.assert_called_once_with(1, 2, 3) + +There is also `patch.dict` for setting values in a dictionary just during the +scope of a test and restoring the dictionary to its original state when the +test ends:: + + >>> foo = {'key': 'value'} + >>> original = foo.copy() + >>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True): + ... assert foo == {'newkey': 'newvalue'} + ... + >>> assert foo == original + +Mock supports the mocking of Python magic methods. The easiest way of +using magic methods is with the `MagicMock` class. It allows you to do +things like:: + + >>> from mock import MagicMock + >>> mock = MagicMock() + >>> mock.__str__.return_value = 'foobarbaz' + >>> str(mock) + 'foobarbaz' + >>> mock.__str__.assert_called_once_with() + +Mock allows you to assign functions (or other Mock instances) to magic methods +and they will be called appropriately. The MagicMock class is just a Mock +variant that has all of the magic methods pre-created for you (well - all the +useful ones anyway). + +The following is an example of using magic methods with the ordinary Mock +class:: + + >>> from mock import Mock + >>> mock = Mock() + >>> mock.__str__ = Mock(return_value = 'wheeeeee') + >>> str(mock) + 'wheeeeee' + +For ensuring that the mock objects your tests use have the same api as the +objects they are replacing, you can use "auto-speccing". Auto-speccing can +be done through the `autospec` argument to patch, or the `create_autospec` +function. Auto-speccing creates mock objects that have the same attributes +and methods as the objects they are replacing, and any functions and methods +(including constructors) have the same call signature as the real object. + +This ensures that your mocks will fail in the same way as your production +code if they are used incorrectly:: + + >>> from mock import create_autospec + >>> def function(a, b, c): + ... pass + ... + >>> mock_function = create_autospec(function, return_value='fishy') + >>> mock_function(1, 2, 3) + 'fishy' + >>> mock_function.assert_called_once_with(1, 2, 3) + >>> mock_function('wrong arguments') + Traceback (most recent call last): + ... + TypeError: () takes exactly 3 arguments (1 given) + +`create_autospec` can also be used on classes, where it copies the signature of +the `__init__` method, and on callable objects where it copies the signature of +the `__call__` method. + +The distribution contains tests and documentation. The tests require +`unittest2 `_ to run. + +Docs from the in-development version of `mock` can be found at +`mock.readthedocs.org `_. diff --git a/python/mock-1.0.0/docs/changelog.txt b/python/mock-1.0.0/docs/changelog.txt new file mode 100644 index 00000000000..a605be3d97b --- /dev/null +++ b/python/mock-1.0.0/docs/changelog.txt @@ -0,0 +1,725 @@ +.. currentmodule:: mock + + +CHANGELOG +========= + +2012/10/07 Version 1.0.0 +------------------------ + +No changes since 1.0.0 beta 1. This version has feature parity with +`unittest.mock +`_ +in Python 3.3. + +Full list of changes since 0.8: + +* `mocksignature`, along with the `mocksignature` argument to `patch`, removed +* Support for deleting attributes (accessing deleted attributes will raise an + `AttributeError`) +* Added the `mock_open` helper function for mocking the builtin `open` +* `__class__` is assignable, so a mock can pass an `isinstance` check without + requiring a spec +* Addition of `PropertyMock`, for mocking properties +* `MagicMocks` made unorderable by default (in Python 3). The comparison + methods (other than equality and inequality) now return `NotImplemented` +* Propagate traceback info to support subclassing of `_patch` by other + libraries +* `create_autospec` works with attributes present in results of `dir` that + can't be fetched from the object's class. Contributed by Konstantine Rybnikov +* Any exceptions in an iterable `side_effect` will be raised instead of + returned +* In Python 3, `create_autospec` now supports keyword only arguments +* Added `patch.stopall` method to stop all active patches created by `start` +* BUGFIX: calling `MagicMock.reset_mock` wouldn't reset magic method mocks +* BUGFIX: calling `reset_mock` on a `MagicMock` created with autospec could + raise an exception +* BUGFIX: passing multiple spec arguments to patchers (`spec` , `spec_set` and + `autospec`) had unpredictable results, now it is an error +* BUGFIX: using `spec=True` *and* `create=True` as arguments to patchers could + result in using `DEFAULT` as the spec. Now it is an error instead +* BUGFIX: using `spec` or `autospec` arguments to patchers, along with + `spec_set=True` did not work correctly +* BUGFIX: using an object that evaluates to False as a spec could be ignored +* BUGFIX: a list as the `spec` argument to a patcher would always result in a + non-callable mock. Now if `__call__` is in the spec the mock is callable + + +2012/07/13 Version 1.0.0 beta 1 +-------------------------------- + +* Added `patch.stopall` method to stop all active patches created by `start` +* BUGFIX: calling `MagicMock.reset_mock` wouldn't reset magic method mocks +* BUGFIX: calling `reset_mock` on a `MagicMock` created with autospec could + raise an exception + + +2012/05/04 Version 1.0.0 alpha 2 +-------------------------------- + +* `PropertyMock` attributes are now standard `MagicMocks` +* `create_autospec` works with attributes present in results of `dir` that + can't be fetched from the object's class. Contributed by Konstantine Rybnikov +* Any exceptions in an iterable `side_effect` will be raised instead of + returned +* In Python 3, `create_autospec` now supports keyword only arguments + + +2012/03/25 Version 1.0.0 alpha 1 +-------------------------------- + +The standard library version! + +* `mocksignature`, along with the `mocksignature` argument to `patch`, removed +* Support for deleting attributes (accessing deleted attributes will raise an + `AttributeError`) +* Added the `mock_open` helper function for mocking the builtin `open` +* `__class__` is assignable, so a mock can pass an `isinstance` check without + requiring a spec +* Addition of `PropertyMock`, for mocking properties +* `MagicMocks` made unorderable by default (in Python 3). The comparison + methods (other than equality and inequality) now return `NotImplemented` +* Propagate traceback info to support subclassing of `_patch` by other + libraries +* BUGFIX: passing multiple spec arguments to patchers (`spec` , `spec_set` and + `autospec`) had unpredictable results, now it is an error +* BUGFIX: using `spec=True` *and* `create=True` as arguments to patchers could + result in using `DEFAULT` as the spec. Now it is an error instead +* BUGFIX: using `spec` or `autospec` arguments to patchers, along with + `spec_set=True` did not work correctly +* BUGFIX: using an object that evaluates to False as a spec could be ignored +* BUGFIX: a list as the `spec` argument to a patcher would always result in a + non-callable mock. Now if `__call__` is in the spec the mock is callable + + +2012/02/13 Version 0.8.0 +------------------------ + +The only changes since 0.8rc2 are: + +* Improved repr of :data:`sentinel` objects +* :data:`ANY` can be used for comparisons against :data:`call` objects +* The return value of `MagicMock.__iter__` method can be set to + any iterable and isn't required to be an iterator + +Full List of changes since 0.7: + +mock 0.8.0 is the last version that will support Python 2.4. + +* Addition of :attr:`~Mock.mock_calls` list for *all* calls (including magic + methods and chained calls) +* :func:`patch` and :func:`patch.object` now create a :class:`MagicMock` + instead of a :class:`Mock` by default +* The patchers (`patch`, `patch.object` and `patch.dict`), plus `Mock` and + `MagicMock`, take arbitrary keyword arguments for configuration +* New mock method :meth:`~Mock.configure_mock` for setting attributes and + return values / side effects on the mock and its attributes +* New mock assert methods :meth:`~Mock.assert_any_call` and + :meth:`~Mock.assert_has_calls` +* Implemented :ref:`auto-speccing` (recursive, lazy speccing of mocks with + mocked signatures for functions/methods), as the `autospec` argument to + `patch` +* Added the :func:`create_autospec` function for manually creating + 'auto-specced' mocks +* :func:`patch.multiple` for doing multiple patches in a single call, using + keyword arguments +* Setting :attr:`~Mock.side_effect` to an iterable will cause calls to the mock + to return the next value from the iterable +* New `new_callable` argument to `patch` and `patch.object` allowing you to + pass in a class or callable object (instead of `MagicMock`) that will be + called to replace the object being patched +* Addition of :class:`NonCallableMock` and :class:`NonCallableMagicMock`, mocks + without a `__call__` method +* Addition of :meth:`~Mock.mock_add_spec` method for adding (or changing) a + spec on an existing mock +* Protocol methods on :class:`MagicMock` are magic mocks, and are created + lazily on first lookup. This means the result of calling a protocol method is + a `MagicMock` instead of a `Mock` as it was previously +* Addition of :meth:`~Mock.attach_mock` method +* Added :data:`ANY` for ignoring arguments in :meth:`~Mock.assert_called_with` + calls +* Addition of :data:`call` helper object +* Improved repr for mocks +* Improved repr for :attr:`Mock.call_args` and entries in + :attr:`Mock.call_args_list`, :attr:`Mock.method_calls` and + :attr:`Mock.mock_calls` +* Improved repr for :data:`sentinel` objects +* `patch` lookup is done at use time not at decoration time +* In Python 2.6 or more recent, `dir` on a mock will report all the dynamically + created attributes (or the full list of attributes if there is a spec) as + well as all the mock methods and attributes. +* Module level :data:`FILTER_DIR` added to control whether `dir(mock)` filters + private attributes. `True` by default. +* `patch.TEST_PREFIX` for controlling how patchers recognise test methods when + used to decorate a class +* Support for using Java exceptions as a :attr:`~Mock.side_effect` on Jython +* `Mock` call lists (`call_args_list`, `method_calls` & `mock_calls`) are now + custom list objects that allow membership tests for "sub lists" and have + a nicer representation if you `str` or `print` them +* Mocks attached as attributes or return values to other mocks have calls + recorded in `method_calls` and `mock_calls` of the parent (unless a name is + already set on the child) +* Improved failure messages for `assert_called_with` and + `assert_called_once_with` +* The return value of the :class:`MagicMock` `__iter__` method can be set to + any iterable and isn't required to be an iterator +* Added the Mock API (`assert_called_with` etc) to functions created by + :func:`mocksignature` +* Tuples as well as lists can be used to specify allowed methods for `spec` & + `spec_set` arguments +* Calling `stop` on an unstarted patcher fails with a more meaningful error + message +* Renamed the internal classes `Sentinel` and `SentinelObject` to prevent abuse +* BUGFIX: an error creating a patch, with nested patch decorators, won't leave + patches in place +* BUGFIX: `__truediv__` and `__rtruediv__` not available as magic methods on + mocks in Python 3 +* BUGFIX: `assert_called_with` / `assert_called_once_with` can be used with + `self` as a keyword argument +* BUGFIX: when patching a class with an explicit spec / spec_set (not a + boolean) it applies "spec inheritance" to the return value of the created + mock (the "instance") +* BUGFIX: remove the `__unittest` marker causing traceback truncation +* Removal of deprecated `patch_object` +* Private attributes `_name`, `_methods`, '_children', `_wraps` and `_parent` + (etc) renamed to reduce likelihood of clash with user attributes. +* Added license file to the distribution + + +2012/01/10 Version 0.8.0 release candidate 2 +-------------------------------------------- + +* Removed the `configure` keyword argument to `create_autospec` and allow + arbitrary keyword arguments (for the `Mock` constructor) instead +* Fixed `ANY` equality with some types in `assert_called_with` calls +* Switched to a standard Sphinx theme (compatible with + `readthedocs.org `_) + + +2011/12/29 Version 0.8.0 release candidate 1 +-------------------------------------------- + +* `create_autospec` on the return value of a mocked class will use `__call__` + for the signature rather than `__init__` +* Performance improvement instantiating `Mock` and `MagicMock` +* Mocks used as magic methods have the same type as their parent instead of + being hardcoded to `MagicMock` + +Special thanks to Julian Berman for his help with diagnosing and improving +performance in this release. + + +2011/10/09 Version 0.8.0 beta 4 +------------------------------- + +* `patch` lookup is done at use time not at decoration time +* When attaching a Mock to another Mock as a magic method, calls are recorded + in mock_calls +* Addition of `attach_mock` method +* Renamed the internal classes `Sentinel` and `SentinelObject` to prevent abuse +* BUGFIX: various issues around circular references with mocks (setting a mock + return value to be itself etc) + + +2011/08/15 Version 0.8.0 beta 3 +------------------------------- + +* Mocks attached as attributes or return values to other mocks have calls + recorded in `method_calls` and `mock_calls` of the parent (unless a name is + already set on the child) +* Addition of `mock_add_spec` method for adding (or changing) a spec on an + existing mock +* Improved repr for `Mock.call_args` and entries in `Mock.call_args_list`, + `Mock.method_calls` and `Mock.mock_calls` +* Improved repr for mocks +* BUGFIX: minor fixes in the way `mock_calls` is worked out, + especially for "intermediate" mocks in a call chain + + +2011/08/05 Version 0.8.0 beta 2 +------------------------------- + +* Setting `side_effect` to an iterable will cause calls to the mock to return + the next value from the iterable +* Added `assert_any_call` method +* Moved `assert_has_calls` from call lists onto mocks +* BUGFIX: `call_args` and all members of `call_args_list` are two tuples of + `(args, kwargs)` again instead of three tuples of `(name, args, kwargs)` + + +2011/07/25 Version 0.8.0 beta 1 +------------------------------- + +* `patch.TEST_PREFIX` for controlling how patchers recognise test methods when + used to decorate a class +* `Mock` call lists (`call_args_list`, `method_calls` & `mock_calls`) are now + custom list objects that allow membership tests for "sub lists" and have + an `assert_has_calls` method for unordered call checks +* `callargs` changed to *always* be a three-tuple of `(name, args, kwargs)` +* Addition of `mock_calls` list for *all* calls (including magic methods and + chained calls) +* Extension of `call` object to support chained calls and `callargs` for better + comparisons with or without names. `call` object has a `call_list` method for + chained calls +* Added the public `instance` argument to `create_autospec` +* Support for using Java exceptions as a `side_effect` on Jython +* Improved failure messages for `assert_called_with` and + `assert_called_once_with` +* Tuples as well as lists can be used to specify allowed methods for `spec` & + `spec_set` arguments +* BUGFIX: Fixed bug in `patch.multiple` for argument passing when creating + mocks +* Added license file to the distribution + + +2011/07/16 Version 0.8.0 alpha 2 +-------------------------------- + +* `patch.multiple` for doing multiple patches in a single call, using keyword + arguments +* New `new_callable` argument to `patch` and `patch.object` allowing you to + pass in a class or callable object (instead of `MagicMock`) that will be + called to replace the object being patched +* Addition of `NonCallableMock` and `NonCallableMagicMock`, mocks without a + `__call__` method +* Mocks created by `patch` have a `MagicMock` as the `return_value` where a + class is being patched +* `create_autospec` can create non-callable mocks for non-callable objects. + `return_value` mocks of classes will be non-callable unless the class has + a `__call__` method +* `autospec` creates a `MagicMock` without a spec for properties and slot + descriptors, because we don't know the type of object they return +* Removed the "inherit" argument from `create_autospec` +* Calling `stop` on an unstarted patcher fails with a more meaningful error + message +* BUGFIX: an error creating a patch, with nested patch decorators, won't leave + patches in place +* BUGFIX: `__truediv__` and `__rtruediv__` not available as magic methods on + mocks in Python 3 +* BUGFIX: `assert_called_with` / `assert_called_once_with` can be used with + `self` as a keyword argument +* BUGFIX: autospec for functions / methods with an argument named self that + isn't the first argument no longer broken +* BUGFIX: when patching a class with an explicit spec / spec_set (not a + boolean) it applies "spec inheritance" to the return value of the created + mock (the "instance") +* BUGFIX: remove the `__unittest` marker causing traceback truncation + + +2011/06/14 Version 0.8.0 alpha 1 +-------------------------------- + +mock 0.8.0 is the last version that will support Python 2.4. + +* The patchers (`patch`, `patch.object` and `patch.dict`), plus `Mock` and + `MagicMock`, take arbitrary keyword arguments for configuration +* New mock method `configure_mock` for setting attributes and return values / + side effects on the mock and its attributes +* In Python 2.6 or more recent, `dir` on a mock will report all the dynamically + created attributes (or the full list of attributes if there is a spec) as + well as all the mock methods and attributes. +* Module level `FILTER_DIR` added to control whether `dir(mock)` filters + private attributes. `True` by default. Note that `vars(Mock())` can still be + used to get all instance attributes and `dir(type(Mock())` will still return + all the other attributes (irrespective of `FILTER_DIR`) +* `patch` and `patch.object` now create a `MagicMock` instead of a `Mock` by + default +* Added `ANY` for ignoring arguments in `assert_called_with` calls +* Addition of `call` helper object +* Protocol methods on `MagicMock` are magic mocks, and are created lazily on + first lookup. This means the result of calling a protocol method is a + MagicMock instead of a Mock as it was previously +* Added the Mock API (`assert_called_with` etc) to functions created by + `mocksignature` +* Private attributes `_name`, `_methods`, '_children', `_wraps` and `_parent` + (etc) renamed to reduce likelihood of clash with user attributes. +* Implemented auto-speccing (recursive, lazy speccing of mocks with mocked + signatures for functions/methods) + + Limitations: + + - Doesn't mock magic methods or attributes (it creates MagicMocks, so the + magic methods are *there*, they just don't have the signature mocked nor + are attributes followed) + - Doesn't mock function / method attributes + - Uses object traversal on the objects being mocked to determine types - so + properties etc may be triggered + - The return value of mocked classes (the 'instance') has the same call + signature as the class __init__ (as they share the same spec) + + You create auto-specced mocks by passing `autospec=True` to `patch`. + + Note that attributes that are None are special cased and mocked without a + spec (so any attribute / method can be used). This is because None is + typically used as a default value for attributes that may be of some other + type, and as we don't know what type that may be we allow all access. + + Note that the `autospec` option to `patch` obsoletes the `mocksignature` + option. + +* Added the `create_autospec` function for manually creating 'auto-specced' + mocks +* Removal of deprecated `patch_object` + + +2011/05/30 Version 0.7.2 +------------------------ + +* BUGFIX: instances of list subclasses can now be used as mock specs +* BUGFIX: MagicMock equality / inequality protocol methods changed to use the + default equality / inequality. This is done through a `side_effect` on + the mocks used for `__eq__` / `__ne__` + + +2011/05/06 Version 0.7.1 +------------------------ + +Package fixes contributed by Michael Fladischer. No code changes. + +* Include template in package +* Use isolated binaries for the tox tests +* Unset executable bit on docs +* Fix DOS line endings in getting-started.txt + + +2011/03/05 Version 0.7.0 +------------------------ + +No API changes since 0.7.0 rc1. Many documentation changes including a stylish +new `Sphinx theme `_. + +The full set of changes since 0.6.0 are: + +* Python 3 compatibility +* Ability to mock magic methods with `Mock` and addition of `MagicMock` + with pre-created magic methods +* Addition of `mocksignature` and `mocksignature` argument to `patch` and + `patch.object` +* Addition of `patch.dict` for changing dictionaries during a test +* Ability to use `patch`, `patch.object` and `patch.dict` as class decorators +* Renamed ``patch_object`` to `patch.object` (``patch_object`` is + deprecated) +* Addition of soft comparisons: `call_args`, `call_args_list` and `method_calls` + now return tuple-like objects which compare equal even when empty args + or kwargs are skipped +* patchers (`patch`, `patch.object` and `patch.dict`) have start and stop + methods +* Addition of `assert_called_once_with` method +* Mocks can now be named (`name` argument to constructor) and the name is used + in the repr +* repr of a mock with a spec includes the class name of the spec +* `assert_called_with` works with `python -OO` +* New `spec_set` keyword argument to `Mock` and `patch`. If used, + attempting to *set* an attribute on a mock not on the spec will raise an + `AttributeError` +* Mocks created with a spec can now pass `isinstance` tests (`__class__` + returns the type of the spec) +* Added docstrings to all objects +* Improved failure message for `Mock.assert_called_with` when the mock + has not been called at all +* Decorated functions / methods have their docstring and `__module__` + preserved on Python 2.4. +* BUGFIX: `mock.patch` now works correctly with certain types of objects that + proxy attribute access, like the django settings object +* BUGFIX: mocks are now copyable (thanks to Ned Batchelder for reporting and + diagnosing this) +* BUGFIX: `spec=True` works with old style classes +* BUGFIX: ``help(mock)`` works now (on the module). Can no longer use ``__bases__`` + as a valid sentinel name (thanks to Stephen Emslie for reporting and + diagnosing this) +* BUGFIX: ``side_effect`` now works with ``BaseException`` exceptions like + ``KeyboardInterrupt`` +* BUGFIX: `reset_mock` caused infinite recursion when a mock is set as its own + return value +* BUGFIX: patching the same object twice now restores the patches correctly +* with statement tests now skipped on Python 2.4 +* Tests require unittest2 (or unittest2-py3k) to run +* Tested with `tox `_ on Python 2.4 - 3.2, + jython and pypy (excluding 3.0) +* Added 'build_sphinx' command to setup.py (requires setuptools or distribute) + Thanks to Florian Bauer +* Switched from subversion to mercurial for source code control +* `Konrad Delong `_ added as co-maintainer + + +2011/02/16 Version 0.7.0 RC 1 +----------------------------- + +Changes since beta 4: + +* Tested with jython, pypy and Python 3.2 and 3.1 +* Decorated functions / methods have their docstring and `__module__` + preserved on Python 2.4 +* BUGFIX: `mock.patch` now works correctly with certain types of objects that + proxy attribute access, like the django settings object +* BUGFIX: `reset_mock` caused infinite recursion when a mock is set as its own + return value + + +2010/11/12 Version 0.7.0 beta 4 +------------------------------- + +* patchers (`patch`, `patch.object` and `patch.dict`) have start and stop + methods +* Addition of `assert_called_once_with` method +* repr of a mock with a spec includes the class name of the spec +* `assert_called_with` works with `python -OO` +* New `spec_set` keyword argument to `Mock` and `patch`. If used, + attempting to *set* an attribute on a mock not on the spec will raise an + `AttributeError` +* Attributes and return value of a `MagicMock` are `MagicMock` objects +* Attempting to set an unsupported magic method now raises an `AttributeError` +* `patch.dict` works as a class decorator +* Switched from subversion to mercurial for source code control +* BUGFIX: mocks are now copyable (thanks to Ned Batchelder for reporting and + diagnosing this) +* BUGFIX: `spec=True` works with old style classes +* BUGFIX: `mocksignature=True` can now patch instance methods via + `patch.object` + + +2010/09/18 Version 0.7.0 beta 3 +------------------------------- + +* Using spec with :class:`MagicMock` only pre-creates magic methods in the spec +* Setting a magic method on a mock with a ``spec`` can only be done if the + spec has that method +* Mocks can now be named (`name` argument to constructor) and the name is used + in the repr +* `mocksignature` can now be used with classes (signature based on `__init__`) + and callable objects (signature based on `__call__`) +* Mocks created with a spec can now pass `isinstance` tests (`__class__` + returns the type of the spec) +* Default numeric value for MagicMock is 1 rather than zero (because the + MagicMock bool defaults to True and 0 is False) +* Improved failure message for :meth:`~Mock.assert_called_with` when the mock + has not been called at all +* Adding the following to the set of supported magic methods: + + - ``__getformat__`` and ``__setformat__`` + - pickle methods + - ``__trunc__``, ``__ceil__`` and ``__floor__`` + - ``__sizeof__`` + +* Added 'build_sphinx' command to setup.py (requires setuptools or distribute) + Thanks to Florian Bauer +* with statement tests now skipped on Python 2.4 +* Tests require unittest2 to run on Python 2.7 +* Improved several docstrings and documentation + + +2010/06/23 Version 0.7.0 beta 2 +------------------------------- + +* :func:`patch.dict` works as a context manager as well as a decorator +* ``patch.dict`` takes a string to specify dictionary as well as a dictionary + object. If a string is supplied the name specified is imported +* BUGFIX: ``patch.dict`` restores dictionary even when an exception is raised + + +2010/06/22 Version 0.7.0 beta 1 +------------------------------- + +* Addition of :func:`mocksignature` +* Ability to mock magic methods +* Ability to use ``patch`` and ``patch.object`` as class decorators +* Renamed ``patch_object`` to :func:`patch.object` (``patch_object`` is + deprecated) +* Addition of :class:`MagicMock` class with all magic methods pre-created for you +* Python 3 compatibility (tested with 3.2 but should work with 3.0 & 3.1 as + well) +* Addition of :func:`patch.dict` for changing dictionaries during a test +* Addition of ``mocksignature`` argument to ``patch`` and ``patch.object`` +* ``help(mock)`` works now (on the module). Can no longer use ``__bases__`` + as a valid sentinel name (thanks to Stephen Emslie for reporting and + diagnosing this) +* Addition of soft comparisons: `call_args`, `call_args_list` and `method_calls` + now return tuple-like objects which compare equal even when empty args + or kwargs are skipped +* Added docstrings. +* BUGFIX: ``side_effect`` now works with ``BaseException`` exceptions like + ``KeyboardInterrupt`` +* BUGFIX: patching the same object twice now restores the patches correctly +* The tests now require `unittest2 `_ + to run +* `Konrad Delong `_ added as co-maintainer + + +2009/08/22 Version 0.6.0 +------------------------ + +* New test layout compatible with test discovery +* Descriptors (static methods / class methods etc) can now be patched and + restored correctly +* Mocks can raise exceptions when called by setting ``side_effect`` to an + exception class or instance +* Mocks that wrap objects will not pass on calls to the underlying object if + an explicit return_value is set + + +2009/04/17 Version 0.5.0 +------------------------ + +* Made DEFAULT part of the public api. +* Documentation built with Sphinx. +* ``side_effect`` is now called with the same arguments as the mock is called with and + if returns a non-DEFAULT value that is automatically set as the ``mock.return_value``. +* ``wraps`` keyword argument used for wrapping objects (and passing calls through to the wrapped object). +* ``Mock.reset`` renamed to ``Mock.reset_mock``, as reset is a common API name. +* ``patch`` / ``patch_object`` are now context managers and can be used with ``with``. +* A new 'create' keyword argument to patch and patch_object that allows them to patch + (and unpatch) attributes that don't exist. (Potentially unsafe to use - it can allow + you to have tests that pass when they are testing an API that doesn't exist - use at + your own risk!) +* The methods keyword argument to Mock has been removed and merged with spec. The spec + argument can now be a list of methods or an object to take the spec from. +* Nested patches may now be applied in a different order (created mocks passed + in the opposite order). This is actually a bugfix. +* patch and patch_object now take a spec keyword argument. If spec is + passed in as 'True' then the Mock created will take the object it is replacing + as its spec object. If the object being replaced is a class, then the return + value for the mock will also use the class as a spec. +* A Mock created without a spec will not attempt to mock any magic methods / attributes + (they will raise an ``AttributeError`` instead). + + +2008/10/12 Version 0.4.0 +------------------------ + +* Default return value is now a new mock rather than None +* return_value added as a keyword argument to the constructor +* New method 'assert_called_with' +* Added 'side_effect' attribute / keyword argument called when mock is called +* patch decorator split into two decorators: + + - ``patch_object`` which takes an object and an attribute name to patch + (plus optionally a value to patch with which defaults to a mock object) + - ``patch`` which takes a string specifying a target to patch; in the form + 'package.module.Class.attribute'. (plus optionally a value to + patch with which defaults to a mock object) + +* Can now patch objects with ``None`` +* Change to patch for nose compatibility with error reporting in wrapped functions +* Reset no longer clears children / return value etc - it just resets + call count and call args. It also calls reset on all children (and + the return value if it is a mock). + +Thanks to Konrad Delong, Kevin Dangoor and others for patches and suggestions. + + +2007/12/03 Version 0.3.1 +------------------------- + +``patch`` maintains the name of decorated functions for compatibility with nose +test autodiscovery. + +Tests decorated with ``patch`` that use the two argument form (implicit mock +creation) will receive the mock(s) passed in as extra arguments. + +Thanks to Kevin Dangoor for these changes. + + +2007/11/30 Version 0.3.0 +------------------------- + +Removed ``patch_module``. ``patch`` can now take a string as the first +argument for patching modules. + +The third argument to ``patch`` is optional - a mock will be created by +default if it is not passed in. + + +2007/11/21 Version 0.2.1 +------------------------- + +Bug fix, allows reuse of functions decorated with ``patch`` and ``patch_module``. + + +2007/11/20 Version 0.2.0 +------------------------- + +Added ``spec`` keyword argument for creating ``Mock`` objects from a +specification object. + +Added ``patch`` and ``patch_module`` monkey patching decorators. + +Added ``sentinel`` for convenient access to unique objects. + +Distribution includes unit tests. + + +2007/11/19 Version 0.1.0 +------------------------- + +Initial release. + + +TODO and Limitations +==================== + +Contributions, bug reports and comments welcomed! + +Feature requests and bug reports are handled on the issue tracker: + + * `mock issue tracker `_ + +`wraps` is not integrated with magic methods. + +`patch` could auto-do the patching in the constructor and unpatch in the +destructor. This would be useful in itself, but violates TOOWTDI and would be +unsafe for IronPython & PyPy (non-deterministic calling of destructors). +Destructors aren't called in CPython where there are cycles, but a weak +reference with a callback can be used to get round this. + +`Mock` has several attributes. This makes it unsuitable for mocking objects +that use these attribute names. A way round this would be to provide methods +that *hide* these attributes when needed. In 0.8 many, but not all, of these +attributes are renamed to gain a `_mock` prefix, making it less likely that +they will clash. Any outstanding attributes that haven't been modified with +the prefix should be changed. + +If a patch is started using `patch.start` and then not stopped correctly then +the unpatching is not done. Using weak references it would be possible to +detect and fix this when the patch object itself is garbage collected. This +would be tricky to get right though. + +When a `Mock` is created by `patch`, arbitrary keywords can be used to set +attributes. If `patch` is created with a `spec`, and is replacing a class, then +a `return_value` mock is created. The keyword arguments are not applied to the +child mock, but could be. + +When mocking a class with `patch`, passing in `spec=True` or `autospec=True`, +the mock class has an instance created from the same spec. Should this be the +default behaviour for mocks anyway (mock return values inheriting the spec +from their parent), or should it be controlled by an additional keyword +argument (`inherit`) to the Mock constructor? `create_autospec` does this, so +an additional keyword argument to Mock is probably unnecessary. + +The `mocksignature` argument to `patch` with a non `Mock` passed into +`new_callable` will *probably* cause an error. Should it just be invalid? + +Note that `NonCallableMock` and `NonCallableMagicMock` still have the unused +(and unusable) attributes: `return_value`, `side_effect`, `call_count`, +`call_args` and `call_args_list`. These could be removed or raise errors on +getting / setting. They also have the `assert_called_with` and +`assert_called_once_with` methods. Removing these would be pointless as +fetching them would create a mock (attribute) that could be called without +error. + +Some outstanding technical debt. The way autospeccing mocks function +signatures was copied and modified from `mocksignature`. This could all be +refactored into one set of functions instead of two. The way we tell if +patchers are started and if a patcher is being used for a `patch.multiple` +call are both horrible. There are now a host of helper functions that should +be rationalised. (Probably time to split mock into a package instead of a +module.) + +Passing arbitrary keyword arguments to `create_autospec`, or `patch` with +`autospec`, when mocking a *function* works fine. However, the arbitrary +attributes are set on the created mock - but `create_autospec` returns a +real function (which doesn't have those attributes). However, what is the use +case for using autospec to create functions with attributes that don't exist +on the original? + +`mocksignature`, plus the `call_args_list` and `method_calls` attributes of +`Mock` could all be deprecated. diff --git a/python/mock-1.0.0/docs/compare.txt b/python/mock-1.0.0/docs/compare.txt new file mode 100644 index 00000000000..41555308e2f --- /dev/null +++ b/python/mock-1.0.0/docs/compare.txt @@ -0,0 +1,628 @@ +========================= + Mock Library Comparison +========================= + + +.. testsetup:: + + def assertEqual(a, b): + assert a == b, ("%r != %r" % (a, b)) + + def assertRaises(Exc, func): + try: + func() + except Exc: + return + assert False, ("%s not raised" % Exc) + + sys.modules['somemodule'] = somemodule = mock.Mock(name='somemodule') + class SomeException(Exception): + some_method = method1 = method2 = None + some_other_object = SomeObject = SomeException + + +A side-by-side comparison of how to accomplish some basic tasks with mock and +some other popular Python mocking libraries and frameworks. + +These are: + +* `flexmock `_ +* `mox `_ +* `Mocker `_ +* `dingus `_ +* `fudge `_ + +Popular python mocking frameworks not yet represented here include +`MiniMock `_. + +`pMock `_ (last release 2004 and doesn't import +in recent versions of Python) and +`python-mock `_ (last release 2005) are +intentionally omitted. + +.. note:: + + A more up to date, and tested for all mock libraries (only the mock + examples on this page can be executed as doctests) version of this + comparison is maintained by Gary Bernhardt: + + * `Python Mock Library Comparison + `_ + +This comparison is by no means complete, and also may not be fully idiomatic +for all the libraries represented. *Please* contribute corrections, missing +comparisons, or comparisons for additional libraries to the `mock issue +tracker `_. + +This comparison page was originally created by the `Mox project +`_ and then extended for +`flexmock and mock `_ by +Herman Sheremetyev. Dingus examples written by `Gary Bernhadt +`_. fudge examples +provided by `Kumar McMillan `_. + +.. note:: + + The examples tasks here were originally created by Mox which is a mocking + *framework* rather than a library like mock. The tasks shown naturally + exemplify tasks that frameworks are good at and not the ones they make + harder. In particular you can take a `Mock` or `MagicMock` object and use + it in any way you want with no up-front configuration. The same is also + true for Dingus. + + The examples for mock here assume version 0.7.0. + + +Simple fake object +~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock() + >>> my_mock.some_method.return_value = "calculated value" + >>> my_mock.some_attribute = "value" + >>> assertEqual("calculated value", my_mock.some_method()) + >>> assertEqual("value", my_mock.some_attribute) + +:: + + # Flexmock + mock = flexmock(some_method=lambda: "calculated value", some_attribute="value") + assertEqual("calculated value", mock.some_method()) + assertEqual("value", mock.some_attribute) + + # Mox + mock = mox.MockAnything() + mock.some_method().AndReturn("calculated value") + mock.some_attribute = "value" + mox.Replay(mock) + assertEqual("calculated value", mock.some_method()) + assertEqual("value", mock.some_attribute) + + # Mocker + mock = mocker.mock() + mock.some_method() + mocker.result("calculated value") + mocker.replay() + mock.some_attribute = "value" + assertEqual("calculated value", mock.some_method()) + assertEqual("value", mock.some_attribute) + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus(some_attribute="value", + ... some_method__returns="calculated value") + >>> assertEqual("calculated value", my_dingus.some_method()) + >>> assertEqual("value", my_dingus.some_attribute) + +:: + + >>> # fudge + >>> my_fake = (fudge.Fake() + ... .provides('some_method') + ... .returns("calculated value") + ... .has_attr(some_attribute="value")) + ... + >>> assertEqual("calculated value", my_fake.some_method()) + >>> assertEqual("value", my_fake.some_attribute) + + +Simple mock +~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock() + >>> my_mock.some_method.return_value = "value" + >>> assertEqual("value", my_mock.some_method()) + >>> my_mock.some_method.assert_called_once_with() + +:: + + # Flexmock + mock = flexmock() + mock.should_receive("some_method").and_return("value").once + assertEqual("value", mock.some_method()) + + # Mox + mock = mox.MockAnything() + mock.some_method().AndReturn("value") + mox.Replay(mock) + assertEqual("value", mock.some_method()) + mox.Verify(mock) + + # Mocker + mock = mocker.mock() + mock.some_method() + mocker.result("value") + mocker.replay() + assertEqual("value", mock.some_method()) + mocker.verify() + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus(some_method__returns="value") + >>> assertEqual("value", my_dingus.some_method()) + >>> assert my_dingus.some_method.calls().once() + +:: + + >>> # fudge + >>> @fudge.test + ... def test(): + ... my_fake = (fudge.Fake() + ... .expects('some_method') + ... .returns("value") + ... .times_called(1)) + ... + >>> test() + Traceback (most recent call last): + ... + AssertionError: fake:my_fake.some_method() was not called + + +Creating partial mocks +~~~~~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> SomeObject.some_method = mock.Mock(return_value='value') + >>> assertEqual("value", SomeObject.some_method()) + +:: + + # Flexmock + flexmock(SomeObject).should_receive("some_method").and_return('value') + assertEqual("value", mock.some_method()) + + # Mox + mock = mox.MockObject(SomeObject) + mock.some_method().AndReturn("value") + mox.Replay(mock) + assertEqual("value", mock.some_method()) + mox.Verify(mock) + + # Mocker + mock = mocker.mock(SomeObject) + mock.Get() + mocker.result("value") + mocker.replay() + assertEqual("value", mock.some_method()) + mocker.verify() + +:: + + >>> # Dingus + >>> object = SomeObject + >>> object.some_method = dingus.Dingus(return_value="value") + >>> assertEqual("value", object.some_method()) + +:: + + >>> # fudge + >>> fake = fudge.Fake().is_callable().returns("") + >>> with fudge.patched_context(SomeObject, 'some_method', fake): + ... s = SomeObject() + ... assertEqual("", s.some_method()) + ... + + +Ensure calls are made in specific order +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock(spec=SomeObject) + >>> my_mock.method1() + + >>> my_mock.method2() + + >>> assertEqual(my_mock.mock_calls, [call.method1(), call.method2()]) + +:: + + # Flexmock + mock = flexmock(SomeObject) + mock.should_receive('method1').once.ordered.and_return('first thing') + mock.should_receive('method2').once.ordered.and_return('second thing') + + # Mox + mock = mox.MockObject(SomeObject) + mock.method1().AndReturn('first thing') + mock.method2().AndReturn('second thing') + mox.Replay(mock) + mox.Verify(mock) + + # Mocker + mock = mocker.mock() + with mocker.order(): + mock.method1() + mocker.result('first thing') + mock.method2() + mocker.result('second thing') + mocker.replay() + mocker.verify() + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus() + >>> my_dingus.method1() + + >>> my_dingus.method2() + + >>> assertEqual(['method1', 'method2'], [call.name for call in my_dingus.calls]) + +:: + + >>> # fudge + >>> @fudge.test + ... def test(): + ... my_fake = (fudge.Fake() + ... .remember_order() + ... .expects('method1') + ... .expects('method2')) + ... my_fake.method2() + ... my_fake.method1() + ... + >>> test() + Traceback (most recent call last): + ... + AssertionError: Call #1 was fake:my_fake.method2(); Expected: #1 fake:my_fake.method1(), #2 fake:my_fake.method2(), end + + +Raising exceptions +~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock() + >>> my_mock.some_method.side_effect = SomeException("message") + >>> assertRaises(SomeException, my_mock.some_method) + +:: + + # Flexmock + mock = flexmock() + mock.should_receive("some_method").and_raise(SomeException("message")) + assertRaises(SomeException, mock.some_method) + + # Mox + mock = mox.MockAnything() + mock.some_method().AndRaise(SomeException("message")) + mox.Replay(mock) + assertRaises(SomeException, mock.some_method) + mox.Verify(mock) + + # Mocker + mock = mocker.mock() + mock.some_method() + mocker.throw(SomeException("message")) + mocker.replay() + assertRaises(SomeException, mock.some_method) + mocker.verify() + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus() + >>> my_dingus.some_method = dingus.exception_raiser(SomeException) + >>> assertRaises(SomeException, my_dingus.some_method) + +:: + + >>> # fudge + >>> my_fake = (fudge.Fake() + ... .is_callable() + ... .raises(SomeException("message"))) + ... + >>> my_fake() + Traceback (most recent call last): + ... + SomeException: message + + +Override new instances of a class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> with mock.patch('somemodule.Someclass') as MockClass: + ... MockClass.return_value = some_other_object + ... assertEqual(some_other_object, somemodule.Someclass()) + ... + + +:: + + # Flexmock + flexmock(some_module.SomeClass, new_instances=some_other_object) + assertEqual(some_other_object, some_module.SomeClass()) + + # Mox + # (you will probably have mox.Mox() available as self.mox in a real test) + mox.Mox().StubOutWithMock(some_module, 'SomeClass', use_mock_anything=True) + some_module.SomeClass().AndReturn(some_other_object) + mox.ReplayAll() + assertEqual(some_other_object, some_module.SomeClass()) + + # Mocker + instance = mocker.mock() + klass = mocker.replace(SomeClass, spec=None) + klass('expected', 'args') + mocker.result(instance) + +:: + + >>> # Dingus + >>> MockClass = dingus.Dingus(return_value=some_other_object) + >>> with dingus.patch('somemodule.SomeClass', MockClass): + ... assertEqual(some_other_object, somemodule.SomeClass()) + ... + +:: + + >>> # fudge + >>> @fudge.patch('somemodule.SomeClass') + ... def test(FakeClass): + ... FakeClass.is_callable().returns(some_other_object) + ... assertEqual(some_other_object, somemodule.SomeClass()) + ... + >>> test() + + +Call the same method multiple times +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. note:: + + You don't need to do *any* configuration to call `mock.Mock()` methods + multiple times. Attributes like `call_count`, `call_args_list` and + `method_calls` provide various different ways of making assertions about + how the mock was used. + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock() + >>> my_mock.some_method() + + >>> my_mock.some_method() + + >>> assert my_mock.some_method.call_count >= 2 + +:: + + # Flexmock # (verifies that the method gets called at least twice) + flexmock(some_object).should_receive('some_method').at_least.twice + + # Mox + # (does not support variable number of calls, so you need to create a new entry for each explicit call) + mock = mox.MockObject(some_object) + mock.some_method(mox.IgnoreArg(), mox.IgnoreArg()) + mock.some_method(mox.IgnoreArg(), mox.IgnoreArg()) + mox.Replay(mock) + mox.Verify(mock) + + # Mocker + # (TODO) + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus() + >>> my_dingus.some_method() + + >>> my_dingus.some_method() + + >>> assert len(my_dingus.calls('some_method')) == 2 + +:: + + >>> # fudge + >>> @fudge.test + ... def test(): + ... my_fake = fudge.Fake().expects('some_method').times_called(2) + ... my_fake.some_method() + ... + >>> test() + Traceback (most recent call last): + ... + AssertionError: fake:my_fake.some_method() was called 1 time(s). Expected 2. + + +Mock chained methods +~~~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock() + >>> method3 = my_mock.method1.return_value.method2.return_value.method3 + >>> method3.return_value = 'some value' + >>> assertEqual('some value', my_mock.method1().method2().method3(1, 2)) + >>> method3.assert_called_once_with(1, 2) + +:: + + # Flexmock + # (intermediate method calls are automatically assigned to temporary fake objects + # and can be called with any arguments) + flexmock(some_object).should_receive( + 'method1.method2.method3' + ).with_args(arg1, arg2).and_return('some value') + assertEqual('some_value', some_object.method1().method2().method3(arg1, arg2)) + +:: + + # Mox + mock = mox.MockObject(some_object) + mock2 = mox.MockAnything() + mock3 = mox.MockAnything() + mock.method1().AndReturn(mock1) + mock2.method2().AndReturn(mock2) + mock3.method3(arg1, arg2).AndReturn('some_value') + self.mox.ReplayAll() + assertEqual("some_value", some_object.method1().method2().method3(arg1, arg2)) + self.mox.VerifyAll() + + # Mocker + # (TODO) + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus() + >>> method3 = my_dingus.method1.return_value.method2.return_value.method3 + >>> method3.return_value = 'some value' + >>> assertEqual('some value', my_dingus.method1().method2().method3(1, 2)) + >>> assert method3.calls('()', 1, 2).once() + +:: + + >>> # fudge + >>> @fudge.test + ... def test(): + ... my_fake = fudge.Fake() + ... (my_fake + ... .expects('method1') + ... .returns_fake() + ... .expects('method2') + ... .returns_fake() + ... .expects('method3') + ... .with_args(1, 2) + ... .returns('some value')) + ... assertEqual('some value', my_fake.method1().method2().method3(1, 2)) + ... + >>> test() + + +Mocking a context manager +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Examples for mock, Dingus and fudge only (so far): + +.. doctest:: + + >>> # mock + >>> my_mock = mock.MagicMock() + >>> with my_mock: + ... pass + ... + >>> my_mock.__enter__.assert_called_with() + >>> my_mock.__exit__.assert_called_with(None, None, None) + +:: + + + >>> # Dingus (nothing special here; all dinguses are "magic mocks") + >>> my_dingus = dingus.Dingus() + >>> with my_dingus: + ... pass + ... + >>> assert my_dingus.__enter__.calls() + >>> assert my_dingus.__exit__.calls('()', None, None, None) + +:: + + >>> # fudge + >>> my_fake = fudge.Fake().provides('__enter__').provides('__exit__') + >>> with my_fake: + ... pass + ... + + +Mocking the builtin open used as a context manager +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Example for mock only (so far): + +.. doctest:: + + >>> # mock + >>> my_mock = mock.MagicMock() + >>> with mock.patch('__builtin__.open', my_mock): + ... manager = my_mock.return_value.__enter__.return_value + ... manager.read.return_value = 'some data' + ... with open('foo') as h: + ... data = h.read() + ... + >>> data + 'some data' + >>> my_mock.assert_called_once_with('foo') + +*or*: + +.. doctest:: + + >>> # mock + >>> with mock.patch('__builtin__.open') as my_mock: + ... my_mock.return_value.__enter__ = lambda s: s + ... my_mock.return_value.__exit__ = mock.Mock() + ... my_mock.return_value.read.return_value = 'some data' + ... with open('foo') as h: + ... data = h.read() + ... + >>> data + 'some data' + >>> my_mock.assert_called_once_with('foo') + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus() + >>> with dingus.patch('__builtin__.open', my_dingus): + ... file_ = open.return_value.__enter__.return_value + ... file_.read.return_value = 'some data' + ... with open('foo') as h: + ... data = f.read() + ... + >>> data + 'some data' + >>> assert my_dingus.calls('()', 'foo').once() + +:: + + >>> # fudge + >>> from contextlib import contextmanager + >>> from StringIO import StringIO + >>> @contextmanager + ... def fake_file(filename): + ... yield StringIO('sekrets') + ... + >>> with fudge.patch('__builtin__.open') as fake_open: + ... fake_open.is_callable().calls(fake_file) + ... with open('/etc/password') as f: + ... data = f.read() + ... + fake:__builtin__.open + >>> data + 'sekrets' \ No newline at end of file diff --git a/python/mock-1.0.0/docs/conf.py b/python/mock-1.0.0/docs/conf.py new file mode 100644 index 00000000000..62f0491ccae --- /dev/null +++ b/python/mock-1.0.0/docs/conf.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +# +# Mock documentation build configuration file, created by +# sphinx-quickstart on Mon Nov 17 18:12:00 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default value; values that are commented out +# serve to show the default value. + +import sys, os +sys.path.insert(0, os.path.abspath('..')) +from mock import __version__ + +# If your extensions are in another directory, add it here. If the directory +# is relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. +#sys.path.append(os.path.abspath('some/directory')) + +# General configuration +# --------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.doctest'] + +doctest_global_setup = """ +import os +import sys +import mock +from mock import * # yeah, I know :-/ +import unittest2 +import __main__ + +if os.getcwd() not in sys.path: + sys.path.append(os.getcwd()) + +# keep a reference to __main__ +sys.modules['__main'] = __main__ + +class ProxyModule(object): + def __init__(self): + self.__dict__ = globals() + +sys.modules['__main__'] = ProxyModule() +""" + +doctest_global_cleanup = """ +sys.modules['__main__'] = sys.modules['__main'] +""" + +html_theme = 'nature' +html_theme_options = {} + +# Add any paths that contain templates here, relative to this directory. +#templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.txt' + +# The master toctree document. +master_doc = 'index' + +# General substitutions. +project = u'Mock' +copyright = u'2007-2012, Michael Foord & the mock team' + +# The default replacements for |version| and |release|, also used in various +# other places throughout the built documents. +# +# The short X.Y version. +version = __version__[:3] +# The full version, including alpha/beta/rc tags. +release = __version__ + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directories, that shouldn't be searched +# for source files. +exclude_trees = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +add_module_names = False + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'friendly' + + +# Options for HTML output +# ----------------------- + +# The style sheet to use for HTML and HTML Help pages. A file of that name +# must exist either in Sphinx' static/ path, or in one of the custom paths +# given in html_static_path. +#html_style = 'adctheme.css' + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +html_use_modindex = False + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, the reST sources are included in the HTML build as _sources/. +#html_copy_source = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Mockdoc' + + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +latex_font_size = '12pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', 'Mock.tex', u'Mock Documentation', + u'Michael Foord', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +latex_use_modindex = False \ No newline at end of file diff --git a/python/mock-1.0.0/docs/examples.txt b/python/mock-1.0.0/docs/examples.txt new file mode 100644 index 00000000000..ecb994b156a --- /dev/null +++ b/python/mock-1.0.0/docs/examples.txt @@ -0,0 +1,1063 @@ +.. _further-examples: + +================== + Further Examples +================== + +.. currentmodule:: mock + +.. testsetup:: + + from datetime import date + + BackendProvider = Mock() + sys.modules['mymodule'] = mymodule = Mock(name='mymodule') + + def grob(val): + "First frob and then clear val" + mymodule.frob(val) + val.clear() + + mymodule.frob = lambda val: val + mymodule.grob = grob + mymodule.date = date + + class TestCase(unittest2.TestCase): + def run(self): + result = unittest2.TestResult() + out = unittest2.TestCase.run(self, result) + assert result.wasSuccessful() + + from mock import inPy3k + + + +For comprehensive examples, see the unit tests included in the full source +distribution. + +Here are some more examples for some slightly more advanced scenarios than in +the :ref:`getting started ` guide. + + +Mocking chained calls +===================== + +Mocking chained calls is actually straightforward with mock once you +understand the :attr:`~Mock.return_value` attribute. When a mock is called for +the first time, or you fetch its `return_value` before it has been called, a +new `Mock` is created. + +This means that you can see how the object returned from a call to a mocked +object has been used by interrogating the `return_value` mock: + +.. doctest:: + + >>> mock = Mock() + >>> mock().foo(a=2, b=3) + + >>> mock.return_value.foo.assert_called_with(a=2, b=3) + +From here it is a simple step to configure and then make assertions about +chained calls. Of course another alternative is writing your code in a more +testable way in the first place... + +So, suppose we have some code that looks a little bit like this: + +.. doctest:: + + >>> class Something(object): + ... def __init__(self): + ... self.backend = BackendProvider() + ... def method(self): + ... response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call() + ... # more code + +Assuming that `BackendProvider` is already well tested, how do we test +`method()`? Specifically, we want to test that the code section `# more +code` uses the response object in the correct way. + +As this chain of calls is made from an instance attribute we can monkey patch +the `backend` attribute on a `Something` instance. In this particular case +we are only interested in the return value from the final call to +`start_call` so we don't have much configuration to do. Let's assume the +object it returns is 'file-like', so we'll ensure that our response object +uses the builtin `file` as its `spec`. + +To do this we create a mock instance as our mock backend and create a mock +response object for it. To set the response as the return value for that final +`start_call` we could do this: + + `mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response`. + +We can do that in a slightly nicer way using the :meth:`~Mock.configure_mock` +method to directly set the return value for us: + +.. doctest:: + + >>> something = Something() + >>> mock_response = Mock(spec=file) + >>> mock_backend = Mock() + >>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response} + >>> mock_backend.configure_mock(**config) + +With these we monkey patch the "mock backend" in place and can make the real +call: + +.. doctest:: + + >>> something.backend = mock_backend + >>> something.method() + +Using :attr:`~Mock.mock_calls` we can check the chained call with a single +assert. A chained call is several calls in one line of code, so there will be +several entries in `mock_calls`. We can use :meth:`call.call_list` to create +this list of calls for us: + +.. doctest:: + + >>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call() + >>> call_list = chained.call_list() + >>> assert mock_backend.mock_calls == call_list + + +Partial mocking +=============== + +In some tests I wanted to mock out a call to `datetime.date.today() +`_ to return +a known date, but I didn't want to prevent the code under test from +creating new date objects. Unfortunately `datetime.date` is written in C, and +so I couldn't just monkey-patch out the static `date.today` method. + +I found a simple way of doing this that involved effectively wrapping the date +class with a mock, but passing through calls to the constructor to the real +class (and returning real instances). + +The :func:`patch decorator ` is used here to +mock out the `date` class in the module under test. The :attr:`side_effect` +attribute on the mock date class is then set to a lambda function that returns +a real date. When the mock date class is called a real date will be +constructed and returned by `side_effect`. + +.. doctest:: + + >>> from datetime import date + >>> with patch('mymodule.date') as mock_date: + ... mock_date.today.return_value = date(2010, 10, 8) + ... mock_date.side_effect = lambda *args, **kw: date(*args, **kw) + ... + ... assert mymodule.date.today() == date(2010, 10, 8) + ... assert mymodule.date(2009, 6, 8) == date(2009, 6, 8) + ... + +Note that we don't patch `datetime.date` globally, we patch `date` in the +module that *uses* it. See :ref:`where to patch `. + +When `date.today()` is called a known date is returned, but calls to the +`date(...)` constructor still return normal dates. Without this you can find +yourself having to calculate an expected result using exactly the same +algorithm as the code under test, which is a classic testing anti-pattern. + +Calls to the date constructor are recorded in the `mock_date` attributes +(`call_count` and friends) which may also be useful for your tests. + +An alternative way of dealing with mocking dates, or other builtin classes, +is discussed in `this blog entry +`_. + + +Mocking a Generator Method +========================== + +A Python generator is a function or method that uses the `yield statement +`_ to +return a series of values when iterated over [#]_. + +A generator method / function is called to return the generator object. It is +the generator object that is then iterated over. The protocol method for +iteration is `__iter__ +`_, so we can +mock this using a `MagicMock`. + +Here's an example class with an "iter" method implemented as a generator: + +.. doctest:: + + >>> class Foo(object): + ... def iter(self): + ... for i in [1, 2, 3]: + ... yield i + ... + >>> foo = Foo() + >>> list(foo.iter()) + [1, 2, 3] + + +How would we mock this class, and in particular its "iter" method? + +To configure the values returned from the iteration (implicit in the call to +`list`), we need to configure the object returned by the call to `foo.iter()`. + +.. doctest:: + + >>> mock_foo = MagicMock() + >>> mock_foo.iter.return_value = iter([1, 2, 3]) + >>> list(mock_foo.iter()) + [1, 2, 3] + +.. [#] There are also generator expressions and more `advanced uses + `_ of generators, but we aren't + concerned about them here. A very good introduction to generators and how + powerful they are is: `Generator Tricks for Systems Programmers + `_. + + +Applying the same patch to every test method +============================================ + +If you want several patches in place for multiple test methods the obvious way +is to apply the patch decorators to every method. This can feel like unnecessary +repetition. For Python 2.6 or more recent you can use `patch` (in all its +various forms) as a class decorator. This applies the patches to all test +methods on the class. A test method is identified by methods whose names start +with `test`: + +.. doctest:: + + >>> @patch('mymodule.SomeClass') + ... class MyTest(TestCase): + ... + ... def test_one(self, MockSomeClass): + ... self.assertTrue(mymodule.SomeClass is MockSomeClass) + ... + ... def test_two(self, MockSomeClass): + ... self.assertTrue(mymodule.SomeClass is MockSomeClass) + ... + ... def not_a_test(self): + ... return 'something' + ... + >>> MyTest('test_one').test_one() + >>> MyTest('test_two').test_two() + >>> MyTest('test_two').not_a_test() + 'something' + +An alternative way of managing patches is to use the :ref:`start-and-stop`. +These allow you to move the patching into your `setUp` and `tearDown` methods. + +.. doctest:: + + >>> class MyTest(TestCase): + ... def setUp(self): + ... self.patcher = patch('mymodule.foo') + ... self.mock_foo = self.patcher.start() + ... + ... def test_foo(self): + ... self.assertTrue(mymodule.foo is self.mock_foo) + ... + ... def tearDown(self): + ... self.patcher.stop() + ... + >>> MyTest('test_foo').run() + +If you use this technique you must ensure that the patching is "undone" by +calling `stop`. This can be fiddlier than you might think, because if an +exception is raised in the setUp then tearDown is not called. `unittest2 +`_ cleanup functions make this simpler: + + +.. doctest:: + + >>> class MyTest(TestCase): + ... def setUp(self): + ... patcher = patch('mymodule.foo') + ... self.addCleanup(patcher.stop) + ... self.mock_foo = patcher.start() + ... + ... def test_foo(self): + ... self.assertTrue(mymodule.foo is self.mock_foo) + ... + >>> MyTest('test_foo').run() + + +Mocking Unbound Methods +======================= + +Whilst writing tests today I needed to patch an *unbound method* (patching the +method on the class rather than on the instance). I needed self to be passed +in as the first argument because I want to make asserts about which objects +were calling this particular method. The issue is that you can't patch with a +mock for this, because if you replace an unbound method with a mock it doesn't +become a bound method when fetched from the instance, and so it doesn't get +self passed in. The workaround is to patch the unbound method with a real +function instead. The :func:`patch` decorator makes it so simple to +patch out methods with a mock that having to create a real function becomes a +nuisance. + +If you pass `autospec=True` to patch then it does the patching with a +*real* function object. This function object has the same signature as the one +it is replacing, but delegates to a mock under the hood. You still get your +mock auto-created in exactly the same way as before. What it means though, is +that if you use it to patch out an unbound method on a class the mocked +function will be turned into a bound method if it is fetched from an instance. +It will have `self` passed in as the first argument, which is exactly what I +wanted: + +.. doctest:: + + >>> class Foo(object): + ... def foo(self): + ... pass + ... + >>> with patch.object(Foo, 'foo', autospec=True) as mock_foo: + ... mock_foo.return_value = 'foo' + ... foo = Foo() + ... foo.foo() + ... + 'foo' + >>> mock_foo.assert_called_once_with(foo) + +If we don't use `autospec=True` then the unbound method is patched out +with a Mock instance instead, and isn't called with `self`. + + +Checking multiple calls with mock +================================= + +mock has a nice API for making assertions about how your mock objects are used. + +.. doctest:: + + >>> mock = Mock() + >>> mock.foo_bar.return_value = None + >>> mock.foo_bar('baz', spam='eggs') + >>> mock.foo_bar.assert_called_with('baz', spam='eggs') + +If your mock is only being called once you can use the +:meth:`assert_called_once_with` method that also asserts that the +:attr:`call_count` is one. + +.. doctest:: + + >>> mock.foo_bar.assert_called_once_with('baz', spam='eggs') + >>> mock.foo_bar() + >>> mock.foo_bar.assert_called_once_with('baz', spam='eggs') + Traceback (most recent call last): + ... + AssertionError: Expected to be called once. Called 2 times. + +Both `assert_called_with` and `assert_called_once_with` make assertions about +the *most recent* call. If your mock is going to be called several times, and +you want to make assertions about *all* those calls you can use +:attr:`~Mock.call_args_list`: + +.. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock(1, 2, 3) + >>> mock(4, 5, 6) + >>> mock() + >>> mock.call_args_list + [call(1, 2, 3), call(4, 5, 6), call()] + +The :data:`call` helper makes it easy to make assertions about these calls. You +can build up a list of expected calls and compare it to `call_args_list`. This +looks remarkably similar to the repr of the `call_args_list`: + +.. doctest:: + + >>> expected = [call(1, 2, 3), call(4, 5, 6), call()] + >>> mock.call_args_list == expected + True + + +Coping with mutable arguments +============================= + +Another situation is rare, but can bite you, is when your mock is called with +mutable arguments. `call_args` and `call_args_list` store *references* to the +arguments. If the arguments are mutated by the code under test then you can no +longer make assertions about what the values were when the mock was called. + +Here's some example code that shows the problem. Imagine the following functions +defined in 'mymodule':: + + def frob(val): + pass + + def grob(val): + "First frob and then clear val" + frob(val) + val.clear() + +When we try to test that `grob` calls `frob` with the correct argument look +what happens: + +.. doctest:: + + >>> with patch('mymodule.frob') as mock_frob: + ... val = set([6]) + ... mymodule.grob(val) + ... + >>> val + set([]) + >>> mock_frob.assert_called_with(set([6])) + Traceback (most recent call last): + ... + AssertionError: Expected: ((set([6]),), {}) + Called with: ((set([]),), {}) + +One possibility would be for mock to copy the arguments you pass in. This +could then cause problems if you do assertions that rely on object identity +for equality. + +Here's one solution that uses the :attr:`side_effect` +functionality. If you provide a `side_effect` function for a mock then +`side_effect` will be called with the same args as the mock. This gives us an +opportunity to copy the arguments and store them for later assertions. In this +example I'm using *another* mock to store the arguments so that I can use the +mock methods for doing the assertion. Again a helper function sets this up for +me. + +.. doctest:: + + >>> from copy import deepcopy + >>> from mock import Mock, patch, DEFAULT + >>> def copy_call_args(mock): + ... new_mock = Mock() + ... def side_effect(*args, **kwargs): + ... args = deepcopy(args) + ... kwargs = deepcopy(kwargs) + ... new_mock(*args, **kwargs) + ... return DEFAULT + ... mock.side_effect = side_effect + ... return new_mock + ... + >>> with patch('mymodule.frob') as mock_frob: + ... new_mock = copy_call_args(mock_frob) + ... val = set([6]) + ... mymodule.grob(val) + ... + >>> new_mock.assert_called_with(set([6])) + >>> new_mock.call_args + call(set([6])) + +`copy_call_args` is called with the mock that will be called. It returns a new +mock that we do the assertion on. The `side_effect` function makes a copy of +the args and calls our `new_mock` with the copy. + +.. note:: + + If your mock is only going to be used once there is an easier way of + checking arguments at the point they are called. You can simply do the + checking inside a `side_effect` function. + + .. doctest:: + + >>> def side_effect(arg): + ... assert arg == set([6]) + ... + >>> mock = Mock(side_effect=side_effect) + >>> mock(set([6])) + >>> mock(set()) + Traceback (most recent call last): + ... + AssertionError + +An alternative approach is to create a subclass of `Mock` or `MagicMock` that +copies (using `copy.deepcopy +`_) the arguments. +Here's an example implementation: + +.. doctest:: + + >>> from copy import deepcopy + >>> class CopyingMock(MagicMock): + ... def __call__(self, *args, **kwargs): + ... args = deepcopy(args) + ... kwargs = deepcopy(kwargs) + ... return super(CopyingMock, self).__call__(*args, **kwargs) + ... + >>> c = CopyingMock(return_value=None) + >>> arg = set() + >>> c(arg) + >>> arg.add(1) + >>> c.assert_called_with(set()) + >>> c.assert_called_with(arg) + Traceback (most recent call last): + ... + AssertionError: Expected call: mock(set([1])) + Actual call: mock(set([])) + >>> c.foo + + +When you subclass `Mock` or `MagicMock` all dynamically created attributes, +and the `return_value` will use your subclass automatically. That means all +children of a `CopyingMock` will also have the type `CopyingMock`. + + +Raising exceptions on attribute access +====================================== + +You can use :class:`PropertyMock` to mimic the behaviour of properties. This +includes raising exceptions when an attribute is accessed. + +Here's an example raising a `ValueError` when the 'foo' attribute is accessed: + +.. doctest:: + + >>> m = MagicMock() + >>> p = PropertyMock(side_effect=ValueError) + >>> type(m).foo = p + >>> m.foo + Traceback (most recent call last): + .... + ValueError + +Because every mock object has its own type, a new subclass of whichever mock +class you're using, all mock objects are isolated from each other. You can +safely attach properties (or other descriptors or whatever you want in fact) +to `type(mock)` without affecting other mock objects. + + +Multiple calls with different effects +===================================== + +.. note:: + + In mock 1.0 the handling of iterable `side_effect` was changed. Any + exceptions in the iterable will be raised instead of returned. + +Handling code that needs to behave differently on subsequent calls during the +test can be tricky. For example you may have a function that needs to raise +an exception the first time it is called but returns a response on the second +call (testing retry behaviour). + +One approach is to use a :attr:`side_effect` function that replaces itself. The +first time it is called the `side_effect` sets a new `side_effect` that will +be used for the second call. It then raises an exception: + +.. doctest:: + + >>> def side_effect(*args): + ... def second_call(*args): + ... return 'response' + ... mock.side_effect = second_call + ... raise Exception('boom') + ... + >>> mock = Mock(side_effect=side_effect) + >>> mock('first') + Traceback (most recent call last): + ... + Exception: boom + >>> mock('second') + 'response' + >>> mock.assert_called_with('second') + +Another perfectly valid way would be to pop return values from a list. If the +return value is an exception, raise it instead of returning it: + +.. doctest:: + + >>> returns = [Exception('boom'), 'response'] + >>> def side_effect(*args): + ... result = returns.pop(0) + ... if isinstance(result, Exception): + ... raise result + ... return result + ... + >>> mock = Mock(side_effect=side_effect) + >>> mock('first') + Traceback (most recent call last): + ... + Exception: boom + >>> mock('second') + 'response' + >>> mock.assert_called_with('second') + +Which approach you prefer is a matter of taste. The first approach is actually +a line shorter but maybe the second approach is more readable. + + +Nesting Patches +=============== + +Using patch as a context manager is nice, but if you do multiple patches you +can end up with nested with statements indenting further and further to the +right: + +.. doctest:: + + >>> class MyTest(TestCase): + ... + ... def test_foo(self): + ... with patch('mymodule.Foo') as mock_foo: + ... with patch('mymodule.Bar') as mock_bar: + ... with patch('mymodule.Spam') as mock_spam: + ... assert mymodule.Foo is mock_foo + ... assert mymodule.Bar is mock_bar + ... assert mymodule.Spam is mock_spam + ... + >>> original = mymodule.Foo + >>> MyTest('test_foo').test_foo() + >>> assert mymodule.Foo is original + +With unittest2_ `cleanup` functions and the :ref:`start-and-stop` we can +achieve the same effect without the nested indentation. A simple helper +method, `create_patch`, puts the patch in place and returns the created mock +for us: + +.. doctest:: + + >>> class MyTest(TestCase): + ... + ... def create_patch(self, name): + ... patcher = patch(name) + ... thing = patcher.start() + ... self.addCleanup(patcher.stop) + ... return thing + ... + ... def test_foo(self): + ... mock_foo = self.create_patch('mymodule.Foo') + ... mock_bar = self.create_patch('mymodule.Bar') + ... mock_spam = self.create_patch('mymodule.Spam') + ... + ... assert mymodule.Foo is mock_foo + ... assert mymodule.Bar is mock_bar + ... assert mymodule.Spam is mock_spam + ... + >>> original = mymodule.Foo + >>> MyTest('test_foo').run() + >>> assert mymodule.Foo is original + + +Mocking a dictionary with MagicMock +=================================== + +You may want to mock a dictionary, or other container object, recording all +access to it whilst having it still behave like a dictionary. + +We can do this with :class:`MagicMock`, which will behave like a dictionary, +and using :data:`~Mock.side_effect` to delegate dictionary access to a real +underlying dictionary that is under our control. + +When the `__getitem__` and `__setitem__` methods of our `MagicMock` are called +(normal dictionary access) then `side_effect` is called with the key (and in +the case of `__setitem__` the value too). We can also control what is returned. + +After the `MagicMock` has been used we can use attributes like +:data:`~Mock.call_args_list` to assert about how the dictionary was used: + +.. doctest:: + + >>> my_dict = {'a': 1, 'b': 2, 'c': 3} + >>> def getitem(name): + ... return my_dict[name] + ... + >>> def setitem(name, val): + ... my_dict[name] = val + ... + >>> mock = MagicMock() + >>> mock.__getitem__.side_effect = getitem + >>> mock.__setitem__.side_effect = setitem + +.. note:: + + An alternative to using `MagicMock` is to use `Mock` and *only* provide + the magic methods you specifically want: + + .. doctest:: + + >>> mock = Mock() + >>> mock.__setitem__ = Mock(side_effect=getitem) + >>> mock.__getitem__ = Mock(side_effect=setitem) + + A *third* option is to use `MagicMock` but passing in `dict` as the `spec` + (or `spec_set`) argument so that the `MagicMock` created only has + dictionary magic methods available: + + .. doctest:: + + >>> mock = MagicMock(spec_set=dict) + >>> mock.__getitem__.side_effect = getitem + >>> mock.__setitem__.side_effect = setitem + +With these side effect functions in place, the `mock` will behave like a normal +dictionary but recording the access. It even raises a `KeyError` if you try +to access a key that doesn't exist. + +.. doctest:: + + >>> mock['a'] + 1 + >>> mock['c'] + 3 + >>> mock['d'] + Traceback (most recent call last): + ... + KeyError: 'd' + >>> mock['b'] = 'fish' + >>> mock['d'] = 'eggs' + >>> mock['b'] + 'fish' + >>> mock['d'] + 'eggs' + +After it has been used you can make assertions about the access using the normal +mock methods and attributes: + +.. doctest:: + + >>> mock.__getitem__.call_args_list + [call('a'), call('c'), call('d'), call('b'), call('d')] + >>> mock.__setitem__.call_args_list + [call('b', 'fish'), call('d', 'eggs')] + >>> my_dict + {'a': 1, 'c': 3, 'b': 'fish', 'd': 'eggs'} + + +Mock subclasses and their attributes +==================================== + +There are various reasons why you might want to subclass `Mock`. One reason +might be to add helper methods. Here's a silly example: + +.. doctest:: + + >>> class MyMock(MagicMock): + ... def has_been_called(self): + ... return self.called + ... + >>> mymock = MyMock(return_value=None) + >>> mymock + + >>> mymock.has_been_called() + False + >>> mymock() + >>> mymock.has_been_called() + True + +The standard behaviour for `Mock` instances is that attributes and the return +value mocks are of the same type as the mock they are accessed on. This ensures +that `Mock` attributes are `Mocks` and `MagicMock` attributes are `MagicMocks` +[#]_. So if you're subclassing to add helper methods then they'll also be +available on the attributes and return value mock of instances of your +subclass. + +.. doctest:: + + >>> mymock.foo + + >>> mymock.foo.has_been_called() + False + >>> mymock.foo() + + >>> mymock.foo.has_been_called() + True + +Sometimes this is inconvenient. For example, `one user +`_ is subclassing mock to +created a `Twisted adaptor +`_. +Having this applied to attributes too actually causes errors. + +`Mock` (in all its flavours) uses a method called `_get_child_mock` to create +these "sub-mocks" for attributes and return values. You can prevent your +subclass being used for attributes by overriding this method. The signature is +that it takes arbitrary keyword arguments (`**kwargs`) which are then passed +onto the mock constructor: + +.. doctest:: + + >>> class Subclass(MagicMock): + ... def _get_child_mock(self, **kwargs): + ... return MagicMock(**kwargs) + ... + >>> mymock = Subclass() + >>> mymock.foo + + >>> assert isinstance(mymock, Subclass) + >>> assert not isinstance(mymock.foo, Subclass) + >>> assert not isinstance(mymock(), Subclass) + +.. [#] An exception to this rule are the non-callable mocks. Attributes use the + callable variant because otherwise non-callable mocks couldn't have callable + methods. + + +Mocking imports with patch.dict +=============================== + +One situation where mocking can be hard is where you have a local import inside +a function. These are harder to mock because they aren't using an object from +the module namespace that we can patch out. + +Generally local imports are to be avoided. They are sometimes done to prevent +circular dependencies, for which there is *usually* a much better way to solve +the problem (refactor the code) or to prevent "up front costs" by delaying the +import. This can also be solved in better ways than an unconditional local +import (store the module as a class or module attribute and only do the import +on first use). + +That aside there is a way to use `mock` to affect the results of an import. +Importing fetches an *object* from the `sys.modules` dictionary. Note that it +fetches an *object*, which need not be a module. Importing a module for the +first time results in a module object being put in `sys.modules`, so usually +when you import something you get a module back. This need not be the case +however. + +This means you can use :func:`patch.dict` to *temporarily* put a mock in place +in `sys.modules`. Any imports whilst this patch is active will fetch the mock. +When the patch is complete (the decorated function exits, the with statement +body is complete or `patcher.stop()` is called) then whatever was there +previously will be restored safely. + +Here's an example that mocks out the 'fooble' module. + +.. doctest:: + + >>> mock = Mock() + >>> with patch.dict('sys.modules', {'fooble': mock}): + ... import fooble + ... fooble.blob() + ... + + >>> assert 'fooble' not in sys.modules + >>> mock.blob.assert_called_once_with() + +As you can see the `import fooble` succeeds, but on exit there is no 'fooble' +left in `sys.modules`. + +This also works for the `from module import name` form: + +.. doctest:: + + >>> mock = Mock() + >>> with patch.dict('sys.modules', {'fooble': mock}): + ... from fooble import blob + ... blob.blip() + ... + + >>> mock.blob.blip.assert_called_once_with() + +With slightly more work you can also mock package imports: + +.. doctest:: + + >>> mock = Mock() + >>> modules = {'package': mock, 'package.module': mock.module} + >>> with patch.dict('sys.modules', modules): + ... from package.module import fooble + ... fooble() + ... + + >>> mock.module.fooble.assert_called_once_with() + + +Tracking order of calls and less verbose call assertions +======================================================== + +The :class:`Mock` class allows you to track the *order* of method calls on +your mock objects through the :attr:`~Mock.method_calls` attribute. This +doesn't allow you to track the order of calls between separate mock objects, +however we can use :attr:`~Mock.mock_calls` to achieve the same effect. + +Because mocks track calls to child mocks in `mock_calls`, and accessing an +arbitrary attribute of a mock creates a child mock, we can create our separate +mocks from a parent one. Calls to those child mock will then all be recorded, +in order, in the `mock_calls` of the parent: + +.. doctest:: + + >>> manager = Mock() + >>> mock_foo = manager.foo + >>> mock_bar = manager.bar + + >>> mock_foo.something() + + >>> mock_bar.other.thing() + + + >>> manager.mock_calls + [call.foo.something(), call.bar.other.thing()] + +We can then assert about the calls, including the order, by comparing with +the `mock_calls` attribute on the manager mock: + +.. doctest:: + + >>> expected_calls = [call.foo.something(), call.bar.other.thing()] + >>> manager.mock_calls == expected_calls + True + +If `patch` is creating, and putting in place, your mocks then you can attach +them to a manager mock using the :meth:`~Mock.attach_mock` method. After +attaching calls will be recorded in `mock_calls` of the manager. + +.. doctest:: + + >>> manager = MagicMock() + >>> with patch('mymodule.Class1') as MockClass1: + ... with patch('mymodule.Class2') as MockClass2: + ... manager.attach_mock(MockClass1, 'MockClass1') + ... manager.attach_mock(MockClass2, 'MockClass2') + ... MockClass1().foo() + ... MockClass2().bar() + ... + + + >>> manager.mock_calls + [call.MockClass1(), + call.MockClass1().foo(), + call.MockClass2(), + call.MockClass2().bar()] + +If many calls have been made, but you're only interested in a particular +sequence of them then an alternative is to use the +:meth:`~Mock.assert_has_calls` method. This takes a list of calls (constructed +with the :data:`call` object). If that sequence of calls are in +:attr:`~Mock.mock_calls` then the assert succeeds. + +.. doctest:: + + >>> m = MagicMock() + >>> m().foo().bar().baz() + + >>> m.one().two().three() + + >>> calls = call.one().two().three().call_list() + >>> m.assert_has_calls(calls) + +Even though the chained call `m.one().two().three()` aren't the only calls that +have been made to the mock, the assert still succeeds. + +Sometimes a mock may have several calls made to it, and you are only interested +in asserting about *some* of those calls. You may not even care about the +order. In this case you can pass `any_order=True` to `assert_has_calls`: + +.. doctest:: + + >>> m = MagicMock() + >>> m(1), m.two(2, 3), m.seven(7), m.fifty('50') + (...) + >>> calls = [call.fifty('50'), call(1), call.seven(7)] + >>> m.assert_has_calls(calls, any_order=True) + + +More complex argument matching +============================== + +Using the same basic concept as `ANY` we can implement matchers to do more +complex assertions on objects used as arguments to mocks. + +Suppose we expect some object to be passed to a mock that by default +compares equal based on object identity (which is the Python default for user +defined classes). To use :meth:`~Mock.assert_called_with` we would need to pass +in the exact same object. If we are only interested in some of the attributes +of this object then we can create a matcher that will check these attributes +for us. + +You can see in this example how a 'standard' call to `assert_called_with` isn't +sufficient: + +.. doctest:: + + >>> class Foo(object): + ... def __init__(self, a, b): + ... self.a, self.b = a, b + ... + >>> mock = Mock(return_value=None) + >>> mock(Foo(1, 2)) + >>> mock.assert_called_with(Foo(1, 2)) + Traceback (most recent call last): + ... + AssertionError: Expected: call(<__main__.Foo object at 0x...>) + Actual call: call(<__main__.Foo object at 0x...>) + +A comparison function for our `Foo` class might look something like this: + +.. doctest:: + + >>> def compare(self, other): + ... if not type(self) == type(other): + ... return False + ... if self.a != other.a: + ... return False + ... if self.b != other.b: + ... return False + ... return True + ... + +And a matcher object that can use comparison functions like this for its +equality operation would look something like this: + +.. doctest:: + + >>> class Matcher(object): + ... def __init__(self, compare, some_obj): + ... self.compare = compare + ... self.some_obj = some_obj + ... def __eq__(self, other): + ... return self.compare(self.some_obj, other) + ... + +Putting all this together: + +.. doctest:: + + >>> match_foo = Matcher(compare, Foo(1, 2)) + >>> mock.assert_called_with(match_foo) + +The `Matcher` is instantiated with our compare function and the `Foo` object +we want to compare against. In `assert_called_with` the `Matcher` equality +method will be called, which compares the object the mock was called with +against the one we created our matcher with. If they match then +`assert_called_with` passes, and if they don't an `AssertionError` is raised: + +.. doctest:: + + >>> match_wrong = Matcher(compare, Foo(3, 4)) + >>> mock.assert_called_with(match_wrong) + Traceback (most recent call last): + ... + AssertionError: Expected: ((,), {}) + Called with: ((,), {}) + +With a bit of tweaking you could have the comparison function raise the +`AssertionError` directly and provide a more useful failure message. + +As of version 1.5, the Python testing library `PyHamcrest +`_ provides similar functionality, +that may be useful here, in the form of its equality matcher +(`hamcrest.library.integration.match_equality +`_). + + +Less verbose configuration of mock objects +========================================== + +This recipe, for easier configuration of mock objects, is now part of `Mock`. +See the :meth:`~Mock.configure_mock` method. + + +Matching any argument in assertions +=================================== + +This example is now built in to mock. See :data:`ANY`. + + +Mocking Properties +================== + +This example is now built in to mock. See :class:`PropertyMock`. + + +Mocking open +============ + +This example is now built in to mock. See :func:`mock_open`. + + +Mocks without some attributes +============================= + +This example is now built in to mock. See :ref:`deleting-attributes`. diff --git a/python/mock-1.0.0/docs/getting-started.txt b/python/mock-1.0.0/docs/getting-started.txt new file mode 100644 index 00000000000..1b5d289ebe8 --- /dev/null +++ b/python/mock-1.0.0/docs/getting-started.txt @@ -0,0 +1,479 @@ +=========================== + Getting Started with Mock +=========================== + +.. _getting-started: + +.. index:: Getting Started + +.. testsetup:: + + class SomeClass(object): + static_method = None + class_method = None + attribute = None + + sys.modules['package'] = package = Mock(name='package') + sys.modules['package.module'] = module = package.module + sys.modules['module'] = package.module + + +Using Mock +========== + +Mock Patching Methods +--------------------- + +Common uses for :class:`Mock` objects include: + +* Patching methods +* Recording method calls on objects + +You might want to replace a method on an object to check that +it is called with the correct arguments by another part of the system: + +.. doctest:: + + >>> real = SomeClass() + >>> real.method = MagicMock(name='method') + >>> real.method(3, 4, 5, key='value') + + +Once our mock has been used (`real.method` in this example) it has methods +and attributes that allow you to make assertions about how it has been used. + +.. note:: + + In most of these examples the :class:`Mock` and :class:`MagicMock` classes + are interchangeable. As the `MagicMock` is the more capable class it makes + a sensible one to use by default. + +Once the mock has been called its :attr:`~Mock.called` attribute is set to +`True`. More importantly we can use the :meth:`~Mock.assert_called_with` or +:meth:`~Mock.assert_called_once_with` method to check that it was called with +the correct arguments. + +This example tests that calling `ProductionClass().method` results in a call to +the `something` method: + +.. doctest:: + + >>> from mock import MagicMock + >>> class ProductionClass(object): + ... def method(self): + ... self.something(1, 2, 3) + ... def something(self, a, b, c): + ... pass + ... + >>> real = ProductionClass() + >>> real.something = MagicMock() + >>> real.method() + >>> real.something.assert_called_once_with(1, 2, 3) + + + +Mock for Method Calls on an Object +---------------------------------- + +In the last example we patched a method directly on an object to check that it +was called correctly. Another common use case is to pass an object into a +method (or some part of the system under test) and then check that it is used +in the correct way. + +The simple `ProductionClass` below has a `closer` method. If it is called with +an object then it calls `close` on it. + +.. doctest:: + + >>> class ProductionClass(object): + ... def closer(self, something): + ... something.close() + ... + +So to test it we need to pass in an object with a `close` method and check +that it was called correctly. + +.. doctest:: + + >>> real = ProductionClass() + >>> mock = Mock() + >>> real.closer(mock) + >>> mock.close.assert_called_with() + +We don't have to do any work to provide the 'close' method on our mock. +Accessing close creates it. So, if 'close' hasn't already been called then +accessing it in the test will create it, but :meth:`~Mock.assert_called_with` +will raise a failure exception. + + +Mocking Classes +--------------- + +A common use case is to mock out classes instantiated by your code under test. +When you patch a class, then that class is replaced with a mock. Instances +are created by *calling the class*. This means you access the "mock instance" +by looking at the return value of the mocked class. + +In the example below we have a function `some_function` that instantiates `Foo` +and calls a method on it. The call to `patch` replaces the class `Foo` with a +mock. The `Foo` instance is the result of calling the mock, so it is configured +by modifying the mock :attr:`~Mock.return_value`. + +.. doctest:: + + >>> def some_function(): + ... instance = module.Foo() + ... return instance.method() + ... + >>> with patch('module.Foo') as mock: + ... instance = mock.return_value + ... instance.method.return_value = 'the result' + ... result = some_function() + ... assert result == 'the result' + + +Naming your mocks +----------------- + +It can be useful to give your mocks a name. The name is shown in the repr of +the mock and can be helpful when the mock appears in test failure messages. The +name is also propagated to attributes or methods of the mock: + +.. doctest:: + + >>> mock = MagicMock(name='foo') + >>> mock + + >>> mock.method + + + +Tracking all Calls +------------------ + +Often you want to track more than a single call to a method. The +:attr:`~Mock.mock_calls` attribute records all calls +to child attributes of the mock - and also to their children. + +.. doctest:: + + >>> mock = MagicMock() + >>> mock.method() + + >>> mock.attribute.method(10, x=53) + + >>> mock.mock_calls + [call.method(), call.attribute.method(10, x=53)] + +If you make an assertion about `mock_calls` and any unexpected methods +have been called, then the assertion will fail. This is useful because as well +as asserting that the calls you expected have been made, you are also checking +that they were made in the right order and with no additional calls: + +You use the :data:`call` object to construct lists for comparing with +`mock_calls`: + +.. doctest:: + + >>> expected = [call.method(), call.attribute.method(10, x=53)] + >>> mock.mock_calls == expected + True + + +Setting Return Values and Attributes +------------------------------------ + +Setting the return values on a mock object is trivially easy: + +.. doctest:: + + >>> mock = Mock() + >>> mock.return_value = 3 + >>> mock() + 3 + +Of course you can do the same for methods on the mock: + +.. doctest:: + + >>> mock = Mock() + >>> mock.method.return_value = 3 + >>> mock.method() + 3 + +The return value can also be set in the constructor: + +.. doctest:: + + >>> mock = Mock(return_value=3) + >>> mock() + 3 + +If you need an attribute setting on your mock, just do it: + +.. doctest:: + + >>> mock = Mock() + >>> mock.x = 3 + >>> mock.x + 3 + +Sometimes you want to mock up a more complex situation, like for example +`mock.connection.cursor().execute("SELECT 1")`. If we wanted this call to +return a list, then we have to configure the result of the nested call. + +We can use :data:`call` to construct the set of calls in a "chained call" like +this for easy assertion afterwards: + + +.. doctest:: + + >>> mock = Mock() + >>> cursor = mock.connection.cursor.return_value + >>> cursor.execute.return_value = ['foo'] + >>> mock.connection.cursor().execute("SELECT 1") + ['foo'] + >>> expected = call.connection.cursor().execute("SELECT 1").call_list() + >>> mock.mock_calls + [call.connection.cursor(), call.connection.cursor().execute('SELECT 1')] + >>> mock.mock_calls == expected + True + +It is the call to `.call_list()` that turns our call object into a list of +calls representing the chained calls. + + + +Raising exceptions with mocks +----------------------------- + +A useful attribute is :attr:`~Mock.side_effect`. If you set this to an +exception class or instance then the exception will be raised when the mock +is called. + +.. doctest:: + + >>> mock = Mock(side_effect=Exception('Boom!')) + >>> mock() + Traceback (most recent call last): + ... + Exception: Boom! + + +Side effect functions and iterables +----------------------------------- + +`side_effect` can also be set to a function or an iterable. The use case for +`side_effect` as an iterable is where your mock is going to be called several +times, and you want each call to return a different value. When you set +`side_effect` to an iterable every call to the mock returns the next value +from the iterable: + +.. doctest:: + + >>> mock = MagicMock(side_effect=[4, 5, 6]) + >>> mock() + 4 + >>> mock() + 5 + >>> mock() + 6 + + +For more advanced use cases, like dynamically varying the return values +depending on what the mock is called with, `side_effect` can be a function. +The function will be called with the same arguments as the mock. Whatever the +function returns is what the call returns: + +.. doctest:: + + >>> vals = {(1, 2): 1, (2, 3): 2} + >>> def side_effect(*args): + ... return vals[args] + ... + >>> mock = MagicMock(side_effect=side_effect) + >>> mock(1, 2) + 1 + >>> mock(2, 3) + 2 + + +Creating a Mock from an Existing Object +--------------------------------------- + +One problem with over use of mocking is that it couples your tests to the +implementation of your mocks rather than your real code. Suppose you have a +class that implements `some_method`. In a test for another class, you +provide a mock of this object that *also* provides `some_method`. If later +you refactor the first class, so that it no longer has `some_method` - then +your tests will continue to pass even though your code is now broken! + +`Mock` allows you to provide an object as a specification for the mock, +using the `spec` keyword argument. Accessing methods / attributes on the +mock that don't exist on your specification object will immediately raise an +attribute error. If you change the implementation of your specification, then +tests that use that class will start failing immediately without you having to +instantiate the class in those tests. + +.. doctest:: + + >>> mock = Mock(spec=SomeClass) + >>> mock.old_method() + Traceback (most recent call last): + ... + AttributeError: object has no attribute 'old_method' + +If you want a stronger form of specification that prevents the setting +of arbitrary attributes as well as the getting of them then you can use +`spec_set` instead of `spec`. + + + +Patch Decorators +================ + +.. note:: + + With `patch` it matters that you patch objects in the namespace where they + are looked up. This is normally straightforward, but for a quick guide + read :ref:`where to patch `. + + +A common need in tests is to patch a class attribute or a module attribute, +for example patching a builtin or patching a class in a module to test that it +is instantiated. Modules and classes are effectively global, so patching on +them has to be undone after the test or the patch will persist into other +tests and cause hard to diagnose problems. + +mock provides three convenient decorators for this: `patch`, `patch.object` and +`patch.dict`. `patch` takes a single string, of the form +`package.module.Class.attribute` to specify the attribute you are patching. It +also optionally takes a value that you want the attribute (or class or +whatever) to be replaced with. 'patch.object' takes an object and the name of +the attribute you would like patched, plus optionally the value to patch it +with. + +`patch.object`: + +.. doctest:: + + >>> original = SomeClass.attribute + >>> @patch.object(SomeClass, 'attribute', sentinel.attribute) + ... def test(): + ... assert SomeClass.attribute == sentinel.attribute + ... + >>> test() + >>> assert SomeClass.attribute == original + + >>> @patch('package.module.attribute', sentinel.attribute) + ... def test(): + ... from package.module import attribute + ... assert attribute is sentinel.attribute + ... + >>> test() + +If you are patching a module (including `__builtin__`) then use `patch` +instead of `patch.object`: + +.. doctest:: + + >>> mock = MagicMock(return_value = sentinel.file_handle) + >>> with patch('__builtin__.open', mock): + ... handle = open('filename', 'r') + ... + >>> mock.assert_called_with('filename', 'r') + >>> assert handle == sentinel.file_handle, "incorrect file handle returned" + +The module name can be 'dotted', in the form `package.module` if needed: + +.. doctest:: + + >>> @patch('package.module.ClassName.attribute', sentinel.attribute) + ... def test(): + ... from package.module import ClassName + ... assert ClassName.attribute == sentinel.attribute + ... + >>> test() + +A nice pattern is to actually decorate test methods themselves: + +.. doctest:: + + >>> class MyTest(unittest2.TestCase): + ... @patch.object(SomeClass, 'attribute', sentinel.attribute) + ... def test_something(self): + ... self.assertEqual(SomeClass.attribute, sentinel.attribute) + ... + >>> original = SomeClass.attribute + >>> MyTest('test_something').test_something() + >>> assert SomeClass.attribute == original + +If you want to patch with a Mock, you can use `patch` with only one argument +(or `patch.object` with two arguments). The mock will be created for you and +passed into the test function / method: + +.. doctest:: + + >>> class MyTest(unittest2.TestCase): + ... @patch.object(SomeClass, 'static_method') + ... def test_something(self, mock_method): + ... SomeClass.static_method() + ... mock_method.assert_called_with() + ... + >>> MyTest('test_something').test_something() + +You can stack up multiple patch decorators using this pattern: + +.. doctest:: + + >>> class MyTest(unittest2.TestCase): + ... @patch('package.module.ClassName1') + ... @patch('package.module.ClassName2') + ... def test_something(self, MockClass2, MockClass1): + ... self.assertTrue(package.module.ClassName1 is MockClass1) + ... self.assertTrue(package.module.ClassName2 is MockClass2) + ... + >>> MyTest('test_something').test_something() + +When you nest patch decorators the mocks are passed in to the decorated +function in the same order they applied (the normal *python* order that +decorators are applied). This means from the bottom up, so in the example +above the mock for `test_module.ClassName2` is passed in first. + +There is also :func:`patch.dict` for setting values in a dictionary just +during a scope and restoring the dictionary to its original state when the test +ends: + +.. doctest:: + + >>> foo = {'key': 'value'} + >>> original = foo.copy() + >>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True): + ... assert foo == {'newkey': 'newvalue'} + ... + >>> assert foo == original + +`patch`, `patch.object` and `patch.dict` can all be used as context managers. + +Where you use `patch` to create a mock for you, you can get a reference to the +mock using the "as" form of the with statement: + +.. doctest:: + + >>> class ProductionClass(object): + ... def method(self): + ... pass + ... + >>> with patch.object(ProductionClass, 'method') as mock_method: + ... mock_method.return_value = None + ... real = ProductionClass() + ... real.method(1, 2, 3) + ... + >>> mock_method.assert_called_with(1, 2, 3) + + +As an alternative `patch`, `patch.object` and `patch.dict` can be used as +class decorators. When used in this way it is the same as applying the +decorator indvidually to every method whose name starts with "test". + +For some more advanced examples, see the :ref:`further-examples` page. diff --git a/python/mock-1.0.0/docs/helpers.txt b/python/mock-1.0.0/docs/helpers.txt new file mode 100644 index 00000000000..571b71d5eb1 --- /dev/null +++ b/python/mock-1.0.0/docs/helpers.txt @@ -0,0 +1,583 @@ +========= + Helpers +========= + +.. currentmodule:: mock + +.. testsetup:: + + mock.FILTER_DIR = True + from pprint import pprint as pp + original_dir = dir + def dir(obj): + print pp(original_dir(obj)) + + import urllib2 + __main__.urllib2 = urllib2 + +.. testcleanup:: + + dir = original_dir + mock.FILTER_DIR = True + + + +call +==== + +.. function:: call(*args, **kwargs) + + `call` is a helper object for making simpler assertions, for comparing + with :attr:`~Mock.call_args`, :attr:`~Mock.call_args_list`, + :attr:`~Mock.mock_calls` and :attr: `~Mock.method_calls`. `call` can also be + used with :meth:`~Mock.assert_has_calls`. + + .. doctest:: + + >>> m = MagicMock(return_value=None) + >>> m(1, 2, a='foo', b='bar') + >>> m() + >>> m.call_args_list == [call(1, 2, a='foo', b='bar'), call()] + True + +.. method:: call.call_list() + + For a call object that represents multiple calls, `call_list` + returns a list of all the intermediate calls as well as the + final call. + +`call_list` is particularly useful for making assertions on "chained calls". A +chained call is multiple calls on a single line of code. This results in +multiple entries in :attr:`~Mock.mock_calls` on a mock. Manually constructing +the sequence of calls can be tedious. + +:meth:`~call.call_list` can construct the sequence of calls from the same +chained call: + +.. doctest:: + + >>> m = MagicMock() + >>> m(1).method(arg='foo').other('bar')(2.0) + + >>> kall = call(1).method(arg='foo').other('bar')(2.0) + >>> kall.call_list() + [call(1), + call().method(arg='foo'), + call().method().other('bar'), + call().method().other()(2.0)] + >>> m.mock_calls == kall.call_list() + True + +.. _calls-as-tuples: + +A `call` object is either a tuple of (positional args, keyword args) or +(name, positional args, keyword args) depending on how it was constructed. When +you construct them yourself this isn't particularly interesting, but the `call` +objects that are in the :attr:`Mock.call_args`, :attr:`Mock.call_args_list` and +:attr:`Mock.mock_calls` attributes can be introspected to get at the individual +arguments they contain. + +The `call` objects in :attr:`Mock.call_args` and :attr:`Mock.call_args_list` +are two-tuples of (positional args, keyword args) whereas the `call` objects +in :attr:`Mock.mock_calls`, along with ones you construct yourself, are +three-tuples of (name, positional args, keyword args). + +You can use their "tupleness" to pull out the individual arguments for more +complex introspection and assertions. The positional arguments are a tuple +(an empty tuple if there are no positional arguments) and the keyword +arguments are a dictionary: + +.. doctest:: + + >>> m = MagicMock(return_value=None) + >>> m(1, 2, 3, arg='one', arg2='two') + >>> kall = m.call_args + >>> args, kwargs = kall + >>> args + (1, 2, 3) + >>> kwargs + {'arg2': 'two', 'arg': 'one'} + >>> args is kall[0] + True + >>> kwargs is kall[1] + True + + >>> m = MagicMock() + >>> m.foo(4, 5, 6, arg='two', arg2='three') + + >>> kall = m.mock_calls[0] + >>> name, args, kwargs = kall + >>> name + 'foo' + >>> args + (4, 5, 6) + >>> kwargs + {'arg2': 'three', 'arg': 'two'} + >>> name is m.mock_calls[0][0] + True + + +create_autospec +=============== + +.. function:: create_autospec(spec, spec_set=False, instance=False, **kwargs) + + Create a mock object using another object as a spec. Attributes on the + mock will use the corresponding attribute on the `spec` object as their + spec. + + Functions or methods being mocked will have their arguments checked to + ensure that they are called with the correct signature. + + If `spec_set` is `True` then attempting to set attributes that don't exist + on the spec object will raise an `AttributeError`. + + If a class is used as a spec then the return value of the mock (the + instance of the class) will have the same spec. You can use a class as the + spec for an instance object by passing `instance=True`. The returned mock + will only be callable if instances of the mock are callable. + + `create_autospec` also takes arbitrary keyword arguments that are passed to + the constructor of the created mock. + +See :ref:`auto-speccing` for examples of how to use auto-speccing with +`create_autospec` and the `autospec` argument to :func:`patch`. + + +ANY +=== + +.. data:: ANY + +Sometimes you may need to make assertions about *some* of the arguments in a +call to mock, but either not care about some of the arguments or want to pull +them individually out of :attr:`~Mock.call_args` and make more complex +assertions on them. + +To ignore certain arguments you can pass in objects that compare equal to +*everything*. Calls to :meth:`~Mock.assert_called_with` and +:meth:`~Mock.assert_called_once_with` will then succeed no matter what was +passed in. + +.. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock('foo', bar=object()) + >>> mock.assert_called_once_with('foo', bar=ANY) + +`ANY` can also be used in comparisons with call lists like +:attr:`~Mock.mock_calls`: + +.. doctest:: + + >>> m = MagicMock(return_value=None) + >>> m(1) + >>> m(1, 2) + >>> m(object()) + >>> m.mock_calls == [call(1), call(1, 2), ANY] + True + + + +FILTER_DIR +========== + +.. data:: FILTER_DIR + +`FILTER_DIR` is a module level variable that controls the way mock objects +respond to `dir` (only for Python 2.6 or more recent). The default is `True`, +which uses the filtering described below, to only show useful members. If you +dislike this filtering, or need to switch it off for diagnostic purposes, then +set `mock.FILTER_DIR = False`. + +With filtering on, `dir(some_mock)` shows only useful attributes and will +include any dynamically created attributes that wouldn't normally be shown. +If the mock was created with a `spec` (or `autospec` of course) then all the +attributes from the original are shown, even if they haven't been accessed +yet: + +.. doctest:: + + >>> dir(Mock()) + ['assert_any_call', + 'assert_called_once_with', + 'assert_called_with', + 'assert_has_calls', + 'attach_mock', + ... + >>> import urllib2 + >>> dir(Mock(spec=urllib2)) + ['AbstractBasicAuthHandler', + 'AbstractDigestAuthHandler', + 'AbstractHTTPHandler', + 'BaseHandler', + ... + +Many of the not-very-useful (private to `Mock` rather than the thing being +mocked) underscore and double underscore prefixed attributes have been +filtered from the result of calling `dir` on a `Mock`. If you dislike this +behaviour you can switch it off by setting the module level switch +`FILTER_DIR`: + +.. doctest:: + + >>> import mock + >>> mock.FILTER_DIR = False + >>> dir(mock.Mock()) + ['_NonCallableMock__get_return_value', + '_NonCallableMock__get_side_effect', + '_NonCallableMock__return_value_doc', + '_NonCallableMock__set_return_value', + '_NonCallableMock__set_side_effect', + '__call__', + '__class__', + ... + +Alternatively you can just use `vars(my_mock)` (instance members) and +`dir(type(my_mock))` (type members) to bypass the filtering irrespective of +`mock.FILTER_DIR`. + + +mock_open +========= + +.. function:: mock_open(mock=None, read_data=None) + + A helper function to create a mock to replace the use of `open`. It works + for `open` called directly or used as a context manager. + + The `mock` argument is the mock object to configure. If `None` (the + default) then a `MagicMock` will be created for you, with the API limited + to methods or attributes available on standard file handles. + + `read_data` is a string for the `read` method of the file handle to return. + This is an empty string by default. + +Using `open` as a context manager is a great way to ensure your file handles +are closed properly and is becoming common:: + + with open('/some/path', 'w') as f: + f.write('something') + +The issue is that even if you mock out the call to `open` it is the +*returned object* that is used as a context manager (and has `__enter__` and +`__exit__` called). + +Mocking context managers with a :class:`MagicMock` is common enough and fiddly +enough that a helper function is useful. + +.. doctest:: + + >>> from mock import mock_open + >>> m = mock_open() + >>> with patch('__main__.open', m, create=True): + ... with open('foo', 'w') as h: + ... h.write('some stuff') + ... + >>> m.mock_calls + [call('foo', 'w'), + call().__enter__(), + call().write('some stuff'), + call().__exit__(None, None, None)] + >>> m.assert_called_once_with('foo', 'w') + >>> handle = m() + >>> handle.write.assert_called_once_with('some stuff') + +And for reading files: + +.. doctest:: + + >>> with patch('__main__.open', mock_open(read_data='bibble'), create=True) as m: + ... with open('foo') as h: + ... result = h.read() + ... + >>> m.assert_called_once_with('foo') + >>> assert result == 'bibble' + + +.. _auto-speccing: + +Autospeccing +============ + +Autospeccing is based on the existing `spec` feature of mock. It limits the +api of mocks to the api of an original object (the spec), but it is recursive +(implemented lazily) so that attributes of mocks only have the same api as +the attributes of the spec. In addition mocked functions / methods have the +same call signature as the original so they raise a `TypeError` if they are +called incorrectly. + +Before I explain how auto-speccing works, here's why it is needed. + +`Mock` is a very powerful and flexible object, but it suffers from two flaws +when used to mock out objects from a system under test. One of these flaws is +specific to the `Mock` api and the other is a more general problem with using +mock objects. + +First the problem specific to `Mock`. `Mock` has two assert methods that are +extremely handy: :meth:`~Mock.assert_called_with` and +:meth:`~Mock.assert_called_once_with`. + +.. doctest:: + + >>> mock = Mock(name='Thing', return_value=None) + >>> mock(1, 2, 3) + >>> mock.assert_called_once_with(1, 2, 3) + >>> mock(1, 2, 3) + >>> mock.assert_called_once_with(1, 2, 3) + Traceback (most recent call last): + ... + AssertionError: Expected to be called once. Called 2 times. + +Because mocks auto-create attributes on demand, and allow you to call them +with arbitrary arguments, if you misspell one of these assert methods then +your assertion is gone: + +.. code-block:: pycon + + >>> mock = Mock(name='Thing', return_value=None) + >>> mock(1, 2, 3) + >>> mock.assret_called_once_with(4, 5, 6) + +Your tests can pass silently and incorrectly because of the typo. + +The second issue is more general to mocking. If you refactor some of your +code, rename members and so on, any tests for code that is still using the +*old api* but uses mocks instead of the real objects will still pass. This +means your tests can all pass even though your code is broken. + +Note that this is another reason why you need integration tests as well as +unit tests. Testing everything in isolation is all fine and dandy, but if you +don't test how your units are "wired together" there is still lots of room +for bugs that tests might have caught. + +`mock` already provides a feature to help with this, called speccing. If you +use a class or instance as the `spec` for a mock then you can only access +attributes on the mock that exist on the real class: + +.. doctest:: + + >>> import urllib2 + >>> mock = Mock(spec=urllib2.Request) + >>> mock.assret_called_with + Traceback (most recent call last): + ... + AttributeError: Mock object has no attribute 'assret_called_with' + +The spec only applies to the mock itself, so we still have the same issue +with any methods on the mock: + +.. code-block:: pycon + + >>> mock.has_data() + + >>> mock.has_data.assret_called_with() + +Auto-speccing solves this problem. You can either pass `autospec=True` to +`patch` / `patch.object` or use the `create_autospec` function to create a +mock with a spec. If you use the `autospec=True` argument to `patch` then the +object that is being replaced will be used as the spec object. Because the +speccing is done "lazily" (the spec is created as attributes on the mock are +accessed) you can use it with very complex or deeply nested objects (like +modules that import modules that import modules) without a big performance +hit. + +Here's an example of it in use: + +.. doctest:: + + >>> import urllib2 + >>> patcher = patch('__main__.urllib2', autospec=True) + >>> mock_urllib2 = patcher.start() + >>> urllib2 is mock_urllib2 + True + >>> urllib2.Request + + +You can see that `urllib2.Request` has a spec. `urllib2.Request` takes two +arguments in the constructor (one of which is `self`). Here's what happens if +we try to call it incorrectly: + +.. doctest:: + + >>> req = urllib2.Request() + Traceback (most recent call last): + ... + TypeError: () takes at least 2 arguments (1 given) + +The spec also applies to instantiated classes (i.e. the return value of +specced mocks): + +.. doctest:: + + >>> req = urllib2.Request('foo') + >>> req + + +`Request` objects are not callable, so the return value of instantiating our +mocked out `urllib2.Request` is a non-callable mock. With the spec in place +any typos in our asserts will raise the correct error: + +.. doctest:: + + >>> req.add_header('spam', 'eggs') + + >>> req.add_header.assret_called_with + Traceback (most recent call last): + ... + AttributeError: Mock object has no attribute 'assret_called_with' + >>> req.add_header.assert_called_with('spam', 'eggs') + +In many cases you will just be able to add `autospec=True` to your existing +`patch` calls and then be protected against bugs due to typos and api +changes. + +As well as using `autospec` through `patch` there is a +:func:`create_autospec` for creating autospecced mocks directly: + +.. doctest:: + + >>> import urllib2 + >>> mock_urllib2 = create_autospec(urllib2) + >>> mock_urllib2.Request('foo', 'bar') + + +This isn't without caveats and limitations however, which is why it is not +the default behaviour. In order to know what attributes are available on the +spec object, autospec has to introspect (access attributes) the spec. As you +traverse attributes on the mock a corresponding traversal of the original +object is happening under the hood. If any of your specced objects have +properties or descriptors that can trigger code execution then you may not be +able to use autospec. On the other hand it is much better to design your +objects so that introspection is safe [#]_. + +A more serious problem is that it is common for instance attributes to be +created in the `__init__` method and not to exist on the class at all. +`autospec` can't know about any dynamically created attributes and restricts +the api to visible attributes. + +.. doctest:: + + >>> class Something(object): + ... def __init__(self): + ... self.a = 33 + ... + >>> with patch('__main__.Something', autospec=True): + ... thing = Something() + ... thing.a + ... + Traceback (most recent call last): + ... + AttributeError: Mock object has no attribute 'a' + +There are a few different ways of resolving this problem. The easiest, but +not necessarily the least annoying, way is to simply set the required +attributes on the mock after creation. Just because `autospec` doesn't allow +you to fetch attributes that don't exist on the spec it doesn't prevent you +setting them: + +.. doctest:: + + >>> with patch('__main__.Something', autospec=True): + ... thing = Something() + ... thing.a = 33 + ... + +There is a more aggressive version of both `spec` and `autospec` that *does* +prevent you setting non-existent attributes. This is useful if you want to +ensure your code only *sets* valid attributes too, but obviously it prevents +this particular scenario: + +.. doctest:: + + >>> with patch('__main__.Something', autospec=True, spec_set=True): + ... thing = Something() + ... thing.a = 33 + ... + Traceback (most recent call last): + ... + AttributeError: Mock object has no attribute 'a' + +Probably the best way of solving the problem is to add class attributes as +default values for instance members initialised in `__init__`. Note that if +you are only setting default attributes in `__init__` then providing them via +class attributes (shared between instances of course) is faster too. e.g. + +.. code-block:: python + + class Something(object): + a = 33 + +This brings up another issue. It is relatively common to provide a default +value of `None` for members that will later be an object of a different type. +`None` would be useless as a spec because it wouldn't let you access *any* +attributes or methods on it. As `None` is *never* going to be useful as a +spec, and probably indicates a member that will normally of some other type, +`autospec` doesn't use a spec for members that are set to `None`. These will +just be ordinary mocks (well - `MagicMocks`): + +.. doctest:: + + >>> class Something(object): + ... member = None + ... + >>> mock = create_autospec(Something) + >>> mock.member.foo.bar.baz() + + +If modifying your production classes to add defaults isn't to your liking +then there are more options. One of these is simply to use an instance as the +spec rather than the class. The other is to create a subclass of the +production class and add the defaults to the subclass without affecting the +production class. Both of these require you to use an alternative object as +the spec. Thankfully `patch` supports this - you can simply pass the +alternative object as the `autospec` argument: + +.. doctest:: + + >>> class Something(object): + ... def __init__(self): + ... self.a = 33 + ... + >>> class SomethingForTest(Something): + ... a = 33 + ... + >>> p = patch('__main__.Something', autospec=SomethingForTest) + >>> mock = p.start() + >>> mock.a + + +.. note:: + + An additional limitation (currently) with `autospec` is that unbound + methods on mocked classes *don't* take an "explicit self" as the first + argument - so this usage will fail with `autospec`. + + .. doctest:: + + >>> class Foo(object): + ... def foo(self): + ... pass + ... + >>> Foo.foo(Foo()) + >>> MockFoo = create_autospec(Foo) + >>> MockFoo.foo(MockFoo()) + Traceback (most recent call last): + ... + TypeError: () takes exactly 1 argument (2 given) + + The reason is that its very hard to tell the difference between functions, + unbound methods and staticmethods across Python 2 & 3 and the alternative + implementations. This restriction may be fixed in future versions. + + +------ + +.. [#] This only applies to classes or already instantiated objects. Calling + a mocked class to create a mock instance *does not* create a real instance. + It is only attribute lookups - along with calls to `dir` - that are done. A + way round this problem would have been to use `getattr_static + `_, + which can fetch attributes without triggering code execution. Descriptors + like `classmethod` and `staticmethod` *need* to be fetched correctly though, + so that their signatures can be mocked correctly. diff --git a/python/mock-1.0.0/docs/index.txt b/python/mock-1.0.0/docs/index.txt new file mode 100644 index 00000000000..7e4a8daca65 --- /dev/null +++ b/python/mock-1.0.0/docs/index.txt @@ -0,0 +1,411 @@ +==================================== + Mock - Mocking and Testing Library +==================================== + +.. currentmodule:: mock + +:Author: `Michael Foord + `_ +:Version: |release| +:Date: 2012/10/07 +:Homepage: `Mock Homepage`_ +:Download: `Mock on PyPI`_ +:Documentation: `PDF Documentation + `_ +:License: `BSD License`_ +:Support: `Mailing list (testing-in-python@lists.idyll.org) + `_ +:Issue tracker: `Google code project + `_ + +.. _Mock Homepage: http://www.voidspace.org.uk/python/mock/ +.. _BSD License: http://www.voidspace.org.uk/python/license.shtml + + +.. currentmodule:: mock + +.. module:: mock + :synopsis: Mock object and testing library. + +.. index:: introduction + +mock is a library for testing in Python. It allows you to replace parts of +your system under test with mock objects and make assertions about how they +have been used. + +mock is now part of the Python standard library, available as `unittest.mock +`_ +in Python 3.3 onwards. + +mock provides a core :class:`Mock` class removing the need to create a host +of stubs throughout your test suite. After performing an action, you can make +assertions about which methods / attributes were used and arguments they were +called with. You can also specify return values and set needed attributes in +the normal way. + +Additionally, mock provides a :func:`patch` decorator that handles patching +module and class level attributes within the scope of a test, along with +:const:`sentinel` for creating unique objects. See the `quick guide`_ for +some examples of how to use :class:`Mock`, :class:`MagicMock` and +:func:`patch`. + +Mock is very easy to use and is designed for use with +`unittest `_. Mock is based on +the 'action -> assertion' pattern instead of `'record -> replay'` used by many +mocking frameworks. + +mock is tested on Python versions 2.4-2.7, Python 3 plus the latest versions of +Jython and PyPy. + + +.. testsetup:: + + class ProductionClass(object): + def method(self, *args): + pass + + module = sys.modules['module'] = ProductionClass + ProductionClass.ClassName1 = ProductionClass + ProductionClass.ClassName2 = ProductionClass + + + +API Documentation +================= + +.. toctree:: + :maxdepth: 2 + + mock + patch + helpers + sentinel + magicmock + + +User Guide +========== + +.. toctree:: + :maxdepth: 2 + + getting-started + examples + compare + changelog + + +.. index:: installing + +Installing +========== + +The current version is |release|. Mock is stable and widely used. If you do +find any bugs, or have suggestions for improvements / extensions +then please contact us. + +* `mock on PyPI `_ +* `mock documentation as PDF + `_ +* `Google Code Home & Mercurial Repository `_ + +.. index:: repository +.. index:: hg + +You can checkout the latest development version from the Google Code Mercurial +repository with the following command: + + ``hg clone https://mock.googlecode.com/hg/ mock`` + + +.. index:: pip +.. index:: easy_install +.. index:: setuptools + +If you have pip, setuptools or distribute you can install mock with: + + | ``easy_install -U mock`` + | ``pip install -U mock`` + +Alternatively you can download the mock distribution from PyPI and after +unpacking run: + + ``python setup.py install`` + + +Quick Guide +=========== + +:class:`Mock` and :class:`MagicMock` objects create all attributes and +methods as you access them and store details of how they have been used. You +can configure them, to specify return values or limit what attributes are +available, and then make assertions about how they have been used: + +.. doctest:: + + >>> from mock import MagicMock + >>> thing = ProductionClass() + >>> thing.method = MagicMock(return_value=3) + >>> thing.method(3, 4, 5, key='value') + 3 + >>> thing.method.assert_called_with(3, 4, 5, key='value') + +:attr:`side_effect` allows you to perform side effects, including raising an +exception when a mock is called: + +.. doctest:: + + >>> mock = Mock(side_effect=KeyError('foo')) + >>> mock() + Traceback (most recent call last): + ... + KeyError: 'foo' + + >>> values = {'a': 1, 'b': 2, 'c': 3} + >>> def side_effect(arg): + ... return values[arg] + ... + >>> mock.side_effect = side_effect + >>> mock('a'), mock('b'), mock('c') + (1, 2, 3) + >>> mock.side_effect = [5, 4, 3, 2, 1] + >>> mock(), mock(), mock() + (5, 4, 3) + +Mock has many other ways you can configure it and control its behaviour. For +example the `spec` argument configures the mock to take its specification +from another object. Attempting to access attributes or methods on the mock +that don't exist on the spec will fail with an `AttributeError`. + +The :func:`patch` decorator / context manager makes it easy to mock classes or +objects in a module under test. The object you specify will be replaced with a +mock (or other object) during the test and restored when the test ends: + +.. doctest:: + + >>> from mock import patch + >>> @patch('module.ClassName2') + ... @patch('module.ClassName1') + ... def test(MockClass1, MockClass2): + ... module.ClassName1() + ... module.ClassName2() + + ... assert MockClass1 is module.ClassName1 + ... assert MockClass2 is module.ClassName2 + ... assert MockClass1.called + ... assert MockClass2.called + ... + >>> test() + +.. note:: + + When you nest patch decorators the mocks are passed in to the decorated + function in the same order they applied (the normal *python* order that + decorators are applied). This means from the bottom up, so in the example + above the mock for `module.ClassName1` is passed in first. + + With `patch` it matters that you patch objects in the namespace where they + are looked up. This is normally straightforward, but for a quick guide + read :ref:`where to patch `. + +As well as a decorator `patch` can be used as a context manager in a with +statement: + +.. doctest:: + + >>> with patch.object(ProductionClass, 'method', return_value=None) as mock_method: + ... thing = ProductionClass() + ... thing.method(1, 2, 3) + ... + >>> mock_method.assert_called_once_with(1, 2, 3) + + +There is also :func:`patch.dict` for setting values in a dictionary just +during a scope and restoring the dictionary to its original state when the test +ends: + +.. doctest:: + + >>> foo = {'key': 'value'} + >>> original = foo.copy() + >>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True): + ... assert foo == {'newkey': 'newvalue'} + ... + >>> assert foo == original + +Mock supports the mocking of Python :ref:`magic methods `. The +easiest way of using magic methods is with the :class:`MagicMock` class. It +allows you to do things like: + +.. doctest:: + + >>> mock = MagicMock() + >>> mock.__str__.return_value = 'foobarbaz' + >>> str(mock) + 'foobarbaz' + >>> mock.__str__.assert_called_with() + +Mock allows you to assign functions (or other Mock instances) to magic methods +and they will be called appropriately. The `MagicMock` class is just a Mock +variant that has all of the magic methods pre-created for you (well, all the +useful ones anyway). + +The following is an example of using magic methods with the ordinary Mock +class: + +.. doctest:: + + >>> mock = Mock() + >>> mock.__str__ = Mock(return_value='wheeeeee') + >>> str(mock) + 'wheeeeee' + +For ensuring that the mock objects in your tests have the same api as the +objects they are replacing, you can use :ref:`auto-speccing `. +Auto-speccing can be done through the `autospec` argument to patch, or the +:func:`create_autospec` function. Auto-speccing creates mock objects that +have the same attributes and methods as the objects they are replacing, and +any functions and methods (including constructors) have the same call +signature as the real object. + +This ensures that your mocks will fail in the same way as your production +code if they are used incorrectly: + +.. doctest:: + + >>> from mock import create_autospec + >>> def function(a, b, c): + ... pass + ... + >>> mock_function = create_autospec(function, return_value='fishy') + >>> mock_function(1, 2, 3) + 'fishy' + >>> mock_function.assert_called_once_with(1, 2, 3) + >>> mock_function('wrong arguments') + Traceback (most recent call last): + ... + TypeError: () takes exactly 3 arguments (1 given) + +`create_autospec` can also be used on classes, where it copies the signature of +the `__init__` method, and on callable objects where it copies the signature of +the `__call__` method. + + +.. index:: references +.. index:: articles + +References +========== + +Articles, blog entries and other stuff related to testing with Mock: + +* `Imposing a No DB Discipline on Django unit tests + `_ +* `mock-django: tools for mocking the Django ORM and models + `_ +* `PyCon 2011 Video: Testing with mock `_ +* `Mock objects in Python + `_ +* `Python: Injecting Mock Objects for Powerful Testing + `_ +* `Python Mock: How to assert a substring of logger output + `_ +* `Mocking Django `_ +* `Mocking dates and other classes that can't be modified + `_ +* `Mock recipes `_ +* `Mockity mock mock - some love for the mock module + `_ +* `Coverage and Mock (with django) + `_ +* `Python Unit Testing with Mock `_ +* `Getting started with Python Mock + `_ +* `Smart Parameter Checks with mock + `_ +* `Python mock testing techniques and tools + `_ +* `How To Test Django Template Tags + `_ +* `A presentation on Unit Testing with Mock + `_ +* `Mocking with Django and Google AppEngine + `_ + + +.. index:: tests +.. index:: unittest2 + +Tests +===== + +Mock uses `unittest2 `_ for its own +test suite. In order to run it, use the `unit2` script that comes with +`unittest2` module on a checkout of the source repository: + + `unit2 discover` + +If you have `setuptools `_ as well as +unittest2 you can run: + + ``python setup.py test`` + +On Python 3.2 you can use ``unittest`` module from the standard library. + + ``python3.2 -m unittest discover`` + +.. index:: Python 3 + +On Python 3 the tests for unicode are skipped as they are not relevant. On +Python 2.4 tests that use the with statements are skipped as the with statement +is invalid syntax on Python 2.4. + + +.. index:: older versions + +Older Versions +============== + +Documentation for older versions of mock: + +* `mock 0.8 `_ +* `mock 0.7 `_ +* `mock 0.6 `_ + +Docs from the in-development version of `mock` can be found at +`mock.readthedocs.org `_. + + +Terminology +=========== + +Terminology for objects used to replace other ones can be confusing. Terms +like double, fake, mock, stub, and spy are all used with varying meanings. + +In `classic mock terminology +`_ +:class:`mock.Mock` is a `spy `_ that +allows for *post-mortem* examination. This is what I call the "action -> +assertion" [#]_ pattern of testing. + +I'm not however a fan of this "statically typed mocking terminology" +promulgated by `Martin Fowler +`_. It confuses usage +patterns with implementation and prevents you from using natural terminology +when discussing mocking. + +I much prefer duck typing, if an object used in your test suite looks like a +mock object and quacks like a mock object then it's fine to call it a mock, no +matter what the implementation looks like. + +This terminology is perhaps more useful in less capable languages where +different usage patterns will *require* different implementations. +`mock.Mock()` is capable of being used in most of the different roles +described by Fowler, except (annoyingly / frustratingly / ironically) a Mock +itself! + +How about a simpler definition: a "mock object" is an object used to replace a +real one in a system under test. + +.. [#] This pattern is called "AAA" by some members of the testing community; + "Arrange - Act - Assert". diff --git a/python/mock-1.0.0/docs/magicmock.txt b/python/mock-1.0.0/docs/magicmock.txt new file mode 100644 index 00000000000..42b2ed9db10 --- /dev/null +++ b/python/mock-1.0.0/docs/magicmock.txt @@ -0,0 +1,258 @@ + +.. currentmodule:: mock + + +.. _magic-methods: + +Mocking Magic Methods +===================== + +.. currentmodule:: mock + +:class:`Mock` supports mocking `magic methods +`_. This allows mock +objects to replace containers or other objects that implement Python +protocols. + +Because magic methods are looked up differently from normal methods [#]_, this +support has been specially implemented. This means that only specific magic +methods are supported. The supported list includes *almost* all of them. If +there are any missing that you need please let us know! + +You mock magic methods by setting the method you are interested in to a function +or a mock instance. If you are using a function then it *must* take ``self`` as +the first argument [#]_. + +.. doctest:: + + >>> def __str__(self): + ... return 'fooble' + ... + >>> mock = Mock() + >>> mock.__str__ = __str__ + >>> str(mock) + 'fooble' + + >>> mock = Mock() + >>> mock.__str__ = Mock() + >>> mock.__str__.return_value = 'fooble' + >>> str(mock) + 'fooble' + + >>> mock = Mock() + >>> mock.__iter__ = Mock(return_value=iter([])) + >>> list(mock) + [] + +One use case for this is for mocking objects used as context managers in a +`with` statement: + +.. doctest:: + + >>> mock = Mock() + >>> mock.__enter__ = Mock(return_value='foo') + >>> mock.__exit__ = Mock(return_value=False) + >>> with mock as m: + ... assert m == 'foo' + ... + >>> mock.__enter__.assert_called_with() + >>> mock.__exit__.assert_called_with(None, None, None) + +Calls to magic methods do not appear in :attr:`~Mock.method_calls`, but they +are recorded in :attr:`~Mock.mock_calls`. + +.. note:: + + If you use the `spec` keyword argument to create a mock then attempting to + set a magic method that isn't in the spec will raise an `AttributeError`. + +The full list of supported magic methods is: + +* ``__hash__``, ``__sizeof__``, ``__repr__`` and ``__str__`` +* ``__dir__``, ``__format__`` and ``__subclasses__`` +* ``__floor__``, ``__trunc__`` and ``__ceil__`` +* Comparisons: ``__cmp__``, ``__lt__``, ``__gt__``, ``__le__``, ``__ge__``, + ``__eq__`` and ``__ne__`` +* Container methods: ``__getitem__``, ``__setitem__``, ``__delitem__``, + ``__contains__``, ``__len__``, ``__iter__``, ``__getslice__``, + ``__setslice__``, ``__reversed__`` and ``__missing__`` +* Context manager: ``__enter__`` and ``__exit__`` +* Unary numeric methods: ``__neg__``, ``__pos__`` and ``__invert__`` +* The numeric methods (including right hand and in-place variants): + ``__add__``, ``__sub__``, ``__mul__``, ``__div__``, + ``__floordiv__``, ``__mod__``, ``__divmod__``, ``__lshift__``, + ``__rshift__``, ``__and__``, ``__xor__``, ``__or__``, and ``__pow__`` +* Numeric conversion methods: ``__complex__``, ``__int__``, ``__float__``, + ``__index__`` and ``__coerce__`` +* Descriptor methods: ``__get__``, ``__set__`` and ``__delete__`` +* Pickling: ``__reduce__``, ``__reduce_ex__``, ``__getinitargs__``, + ``__getnewargs__``, ``__getstate__`` and ``__setstate__`` + + +The following methods are supported in Python 2 but don't exist in Python 3: + +* ``__unicode__``, ``__long__``, ``__oct__``, ``__hex__`` and ``__nonzero__`` +* ``__truediv__`` and ``__rtruediv__`` + +The following methods are supported in Python 3 but don't exist in Python 2: + +* ``__bool__`` and ``__next__`` + +The following methods exist but are *not* supported as they are either in use by +mock, can't be set dynamically, or can cause problems: + +* ``__getattr__``, ``__setattr__``, ``__init__`` and ``__new__`` +* ``__prepare__``, ``__instancecheck__``, ``__subclasscheck__``, ``__del__`` + + + +Magic Mock +========== + +There are two `MagicMock` variants: `MagicMock` and `NonCallableMagicMock`. + + +.. class:: MagicMock(*args, **kw) + + ``MagicMock`` is a subclass of :class:`Mock` with default implementations + of most of the magic methods. You can use ``MagicMock`` without having to + configure the magic methods yourself. + + The constructor parameters have the same meaning as for :class:`Mock`. + + If you use the `spec` or `spec_set` arguments then *only* magic methods + that exist in the spec will be created. + + +.. class:: NonCallableMagicMock(*args, **kw) + + A non-callable version of `MagicMock`. + + The constructor parameters have the same meaning as for + :class:`MagicMock`, with the exception of `return_value` and + `side_effect` which have no meaning on a non-callable mock. + +The magic methods are setup with `MagicMock` objects, so you can configure them +and use them in the usual way: + +.. doctest:: + + >>> mock = MagicMock() + >>> mock[3] = 'fish' + >>> mock.__setitem__.assert_called_with(3, 'fish') + >>> mock.__getitem__.return_value = 'result' + >>> mock[2] + 'result' + +By default many of the protocol methods are required to return objects of a +specific type. These methods are preconfigured with a default return value, so +that they can be used without you having to do anything if you aren't interested +in the return value. You can still *set* the return value manually if you want +to change the default. + +Methods and their defaults: + +* ``__lt__``: NotImplemented +* ``__gt__``: NotImplemented +* ``__le__``: NotImplemented +* ``__ge__``: NotImplemented +* ``__int__`` : 1 +* ``__contains__`` : False +* ``__len__`` : 1 +* ``__iter__`` : iter([]) +* ``__exit__`` : False +* ``__complex__`` : 1j +* ``__float__`` : 1.0 +* ``__bool__`` : True +* ``__nonzero__`` : True +* ``__oct__`` : '1' +* ``__hex__`` : '0x1' +* ``__long__`` : long(1) +* ``__index__`` : 1 +* ``__hash__`` : default hash for the mock +* ``__str__`` : default str for the mock +* ``__unicode__`` : default unicode for the mock +* ``__sizeof__``: default sizeof for the mock + +For example: + +.. doctest:: + + >>> mock = MagicMock() + >>> int(mock) + 1 + >>> len(mock) + 0 + >>> hex(mock) + '0x1' + >>> list(mock) + [] + >>> object() in mock + False + +The two equality method, `__eq__` and `__ne__`, are special (changed in +0.7.2). They do the default equality comparison on identity, using a side +effect, unless you change their return value to return something else: + +.. doctest:: + + >>> MagicMock() == 3 + False + >>> MagicMock() != 3 + True + >>> mock = MagicMock() + >>> mock.__eq__.return_value = True + >>> mock == 3 + True + +In `0.8` the `__iter__` also gained special handling implemented with a +side effect. The return value of `MagicMock.__iter__` can be any iterable +object and isn't required to be an iterator: + +.. doctest:: + + >>> mock = MagicMock() + >>> mock.__iter__.return_value = ['a', 'b', 'c'] + >>> list(mock) + ['a', 'b', 'c'] + >>> list(mock) + ['a', 'b', 'c'] + +If the return value *is* an iterator, then iterating over it once will consume +it and subsequent iterations will result in an empty list: + +.. doctest:: + + >>> mock.__iter__.return_value = iter(['a', 'b', 'c']) + >>> list(mock) + ['a', 'b', 'c'] + >>> list(mock) + [] + +``MagicMock`` has all of the supported magic methods configured except for some +of the obscure and obsolete ones. You can still set these up if you want. + +Magic methods that are supported but not setup by default in ``MagicMock`` are: + +* ``__cmp__`` +* ``__getslice__`` and ``__setslice__`` +* ``__coerce__`` +* ``__subclasses__`` +* ``__dir__`` +* ``__format__`` +* ``__get__``, ``__set__`` and ``__delete__`` +* ``__reversed__`` and ``__missing__`` +* ``__reduce__``, ``__reduce_ex__``, ``__getinitargs__``, ``__getnewargs__``, + ``__getstate__`` and ``__setstate__`` +* ``__getformat__`` and ``__setformat__`` + + + +------------ + +.. [#] Magic methods *should* be looked up on the class rather than the + instance. Different versions of Python are inconsistent about applying this + rule. The supported protocol methods should work with all supported versions + of Python. +.. [#] The function is basically hooked up to the class, but each ``Mock`` + instance is kept isolated from the others. diff --git a/python/mock-1.0.0/docs/mock.txt b/python/mock-1.0.0/docs/mock.txt new file mode 100644 index 00000000000..58712b21a67 --- /dev/null +++ b/python/mock-1.0.0/docs/mock.txt @@ -0,0 +1,842 @@ +The Mock Class +============== + +.. currentmodule:: mock + +.. testsetup:: + + class SomeClass: + pass + + +`Mock` is a flexible mock object intended to replace the use of stubs and +test doubles throughout your code. Mocks are callable and create attributes as +new mocks when you access them [#]_. Accessing the same attribute will always +return the same mock. Mocks record how you use them, allowing you to make +assertions about what your code has done to them. + +:class:`MagicMock` is a subclass of `Mock` with all the magic methods +pre-created and ready to use. There are also non-callable variants, useful +when you are mocking out objects that aren't callable: +:class:`NonCallableMock` and :class:`NonCallableMagicMock` + +The :func:`patch` decorators makes it easy to temporarily replace classes +in a particular module with a `Mock` object. By default `patch` will create +a `MagicMock` for you. You can specify an alternative class of `Mock` using +the `new_callable` argument to `patch`. + + +.. index:: side_effect +.. index:: return_value +.. index:: wraps +.. index:: name +.. index:: spec + +.. class:: Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, **kwargs) + + Create a new `Mock` object. `Mock` takes several optional arguments + that specify the behaviour of the Mock object: + + * `spec`: This can be either a list of strings or an existing object (a + class or instance) that acts as the specification for the mock object. If + you pass in an object then a list of strings is formed by calling dir on + the object (excluding unsupported magic attributes and methods). + Accessing any attribute not in this list will raise an `AttributeError`. + + If `spec` is an object (rather than a list of strings) then + :attr:`__class__` returns the class of the spec object. This allows mocks + to pass `isinstance` tests. + + * `spec_set`: A stricter variant of `spec`. If used, attempting to *set* + or get an attribute on the mock that isn't on the object passed as + `spec_set` will raise an `AttributeError`. + + * `side_effect`: A function to be called whenever the Mock is called. See + the :attr:`~Mock.side_effect` attribute. Useful for raising exceptions or + dynamically changing return values. The function is called with the same + arguments as the mock, and unless it returns :data:`DEFAULT`, the return + value of this function is used as the return value. + + Alternatively `side_effect` can be an exception class or instance. In + this case the exception will be raised when the mock is called. + + If `side_effect` is an iterable then each call to the mock will return + the next value from the iterable. If any of the members of the iterable + are exceptions they will be raised instead of returned. + + A `side_effect` can be cleared by setting it to `None`. + + * `return_value`: The value returned when the mock is called. By default + this is a new Mock (created on first access). See the + :attr:`return_value` attribute. + + * `wraps`: Item for the mock object to wrap. If `wraps` is not None then + calling the Mock will pass the call through to the wrapped object + (returning the real result and ignoring `return_value`). Attribute access + on the mock will return a Mock object that wraps the corresponding + attribute of the wrapped object (so attempting to access an attribute + that doesn't exist will raise an `AttributeError`). + + If the mock has an explicit `return_value` set then calls are not passed + to the wrapped object and the `return_value` is returned instead. + + * `name`: If the mock has a name then it will be used in the repr of the + mock. This can be useful for debugging. The name is propagated to child + mocks. + + Mocks can also be called with arbitrary keyword arguments. These will be + used to set attributes on the mock after it is created. See the + :meth:`configure_mock` method for details. + + + .. method:: assert_called_with(*args, **kwargs) + + This method is a convenient way of asserting that calls are made in a + particular way: + + .. doctest:: + + >>> mock = Mock() + >>> mock.method(1, 2, 3, test='wow') + + >>> mock.method.assert_called_with(1, 2, 3, test='wow') + + + .. method:: assert_called_once_with(*args, **kwargs) + + Assert that the mock was called exactly once and with the specified + arguments. + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock('foo', bar='baz') + >>> mock.assert_called_once_with('foo', bar='baz') + >>> mock('foo', bar='baz') + >>> mock.assert_called_once_with('foo', bar='baz') + Traceback (most recent call last): + ... + AssertionError: Expected to be called once. Called 2 times. + + + .. method:: assert_any_call(*args, **kwargs) + + assert the mock has been called with the specified arguments. + + The assert passes if the mock has *ever* been called, unlike + :meth:`assert_called_with` and :meth:`assert_called_once_with` that + only pass if the call is the most recent one. + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock(1, 2, arg='thing') + >>> mock('some', 'thing', 'else') + >>> mock.assert_any_call(1, 2, arg='thing') + + + .. method:: assert_has_calls(calls, any_order=False) + + assert the mock has been called with the specified calls. + The `mock_calls` list is checked for the calls. + + If `any_order` is False (the default) then the calls must be + sequential. There can be extra calls before or after the + specified calls. + + If `any_order` is True then the calls can be in any order, but + they must all appear in :attr:`mock_calls`. + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock(1) + >>> mock(2) + >>> mock(3) + >>> mock(4) + >>> calls = [call(2), call(3)] + >>> mock.assert_has_calls(calls) + >>> calls = [call(4), call(2), call(3)] + >>> mock.assert_has_calls(calls, any_order=True) + + + .. method:: reset_mock() + + The reset_mock method resets all the call attributes on a mock object: + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock('hello') + >>> mock.called + True + >>> mock.reset_mock() + >>> mock.called + False + + This can be useful where you want to make a series of assertions that + reuse the same object. Note that `reset_mock` *doesn't* clear the + return value, :attr:`side_effect` or any child attributes you have + set using normal assignment. Child mocks and the return value mock + (if any) are reset as well. + + + .. method:: mock_add_spec(spec, spec_set=False) + + Add a spec to a mock. `spec` can either be an object or a + list of strings. Only attributes on the `spec` can be fetched as + attributes from the mock. + + If `spec_set` is `True` then only attributes on the spec can be set. + + + .. method:: attach_mock(mock, attribute) + + Attach a mock as an attribute of this one, replacing its name and + parent. Calls to the attached mock will be recorded in the + :attr:`method_calls` and :attr:`mock_calls` attributes of this one. + + + .. method:: configure_mock(**kwargs) + + Set attributes on the mock through keyword arguments. + + Attributes plus return values and side effects can be set on child + mocks using standard dot notation and unpacking a dictionary in the + method call: + + .. doctest:: + + >>> mock = Mock() + >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} + >>> mock.configure_mock(**attrs) + >>> mock.method() + 3 + >>> mock.other() + Traceback (most recent call last): + ... + KeyError + + The same thing can be achieved in the constructor call to mocks: + + .. doctest:: + + >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} + >>> mock = Mock(some_attribute='eggs', **attrs) + >>> mock.some_attribute + 'eggs' + >>> mock.method() + 3 + >>> mock.other() + Traceback (most recent call last): + ... + KeyError + + `configure_mock` exists to make it easier to do configuration + after the mock has been created. + + + .. method:: __dir__() + + `Mock` objects limit the results of `dir(some_mock)` to useful results. + For mocks with a `spec` this includes all the permitted attributes + for the mock. + + See :data:`FILTER_DIR` for what this filtering does, and how to + switch it off. + + + .. method:: _get_child_mock(**kw) + + Create the child mocks for attributes and return value. + By default child mocks will be the same type as the parent. + Subclasses of Mock may want to override this to customize the way + child mocks are made. + + For non-callable mocks the callable variant will be used (rather than + any custom subclass). + + + .. attribute:: called + + A boolean representing whether or not the mock object has been called: + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock.called + False + >>> mock() + >>> mock.called + True + + .. attribute:: call_count + + An integer telling you how many times the mock object has been called: + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock.call_count + 0 + >>> mock() + >>> mock() + >>> mock.call_count + 2 + + + .. attribute:: return_value + + Set this to configure the value returned by calling the mock: + + .. doctest:: + + >>> mock = Mock() + >>> mock.return_value = 'fish' + >>> mock() + 'fish' + + The default return value is a mock object and you can configure it in + the normal way: + + .. doctest:: + + >>> mock = Mock() + >>> mock.return_value.attribute = sentinel.Attribute + >>> mock.return_value() + + >>> mock.return_value.assert_called_with() + + `return_value` can also be set in the constructor: + + .. doctest:: + + >>> mock = Mock(return_value=3) + >>> mock.return_value + 3 + >>> mock() + 3 + + + .. attribute:: side_effect + + This can either be a function to be called when the mock is called, + or an exception (class or instance) to be raised. + + If you pass in a function it will be called with same arguments as the + mock and unless the function returns the :data:`DEFAULT` singleton the + call to the mock will then return whatever the function returns. If the + function returns :data:`DEFAULT` then the mock will return its normal + value (from the :attr:`return_value`. + + An example of a mock that raises an exception (to test exception + handling of an API): + + .. doctest:: + + >>> mock = Mock() + >>> mock.side_effect = Exception('Boom!') + >>> mock() + Traceback (most recent call last): + ... + Exception: Boom! + + Using `side_effect` to return a sequence of values: + + .. doctest:: + + >>> mock = Mock() + >>> mock.side_effect = [3, 2, 1] + >>> mock(), mock(), mock() + (3, 2, 1) + + The `side_effect` function is called with the same arguments as the + mock (so it is wise for it to take arbitrary args and keyword + arguments) and whatever it returns is used as the return value for + the call. The exception is if `side_effect` returns :data:`DEFAULT`, + in which case the normal :attr:`return_value` is used. + + .. doctest:: + + >>> mock = Mock(return_value=3) + >>> def side_effect(*args, **kwargs): + ... return DEFAULT + ... + >>> mock.side_effect = side_effect + >>> mock() + 3 + + `side_effect` can be set in the constructor. Here's an example that + adds one to the value the mock is called with and returns it: + + .. doctest:: + + >>> side_effect = lambda value: value + 1 + >>> mock = Mock(side_effect=side_effect) + >>> mock(3) + 4 + >>> mock(-8) + -7 + + Setting `side_effect` to `None` clears it: + + .. doctest:: + + >>> from mock import Mock + >>> m = Mock(side_effect=KeyError, return_value=3) + >>> m() + Traceback (most recent call last): + ... + KeyError + >>> m.side_effect = None + >>> m() + 3 + + + .. attribute:: call_args + + This is either `None` (if the mock hasn't been called), or the + arguments that the mock was last called with. This will be in the + form of a tuple: the first member is any ordered arguments the mock + was called with (or an empty tuple) and the second member is any + keyword arguments (or an empty dictionary). + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> print mock.call_args + None + >>> mock() + >>> mock.call_args + call() + >>> mock.call_args == () + True + >>> mock(3, 4) + >>> mock.call_args + call(3, 4) + >>> mock.call_args == ((3, 4),) + True + >>> mock(3, 4, 5, key='fish', next='w00t!') + >>> mock.call_args + call(3, 4, 5, key='fish', next='w00t!') + + `call_args`, along with members of the lists :attr:`call_args_list`, + :attr:`method_calls` and :attr:`mock_calls` are :data:`call` objects. + These are tuples, so they can be unpacked to get at the individual + arguments and make more complex assertions. See + :ref:`calls as tuples `. + + + .. attribute:: call_args_list + + This is a list of all the calls made to the mock object in sequence + (so the length of the list is the number of times it has been + called). Before any calls have been made it is an empty list. The + :data:`call` object can be used for conveniently constructing lists of + calls to compare with `call_args_list`. + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock() + >>> mock(3, 4) + >>> mock(key='fish', next='w00t!') + >>> mock.call_args_list + [call(), call(3, 4), call(key='fish', next='w00t!')] + >>> expected = [(), ((3, 4),), ({'key': 'fish', 'next': 'w00t!'},)] + >>> mock.call_args_list == expected + True + + Members of `call_args_list` are :data:`call` objects. These can be + unpacked as tuples to get at the individual arguments. See + :ref:`calls as tuples `. + + + .. attribute:: method_calls + + As well as tracking calls to themselves, mocks also track calls to + methods and attributes, and *their* methods and attributes: + + .. doctest:: + + >>> mock = Mock() + >>> mock.method() + + >>> mock.property.method.attribute() + + >>> mock.method_calls + [call.method(), call.property.method.attribute()] + + Members of `method_calls` are :data:`call` objects. These can be + unpacked as tuples to get at the individual arguments. See + :ref:`calls as tuples `. + + + .. attribute:: mock_calls + + `mock_calls` records *all* calls to the mock object, its methods, magic + methods *and* return value mocks. + + .. doctest:: + + >>> mock = MagicMock() + >>> result = mock(1, 2, 3) + >>> mock.first(a=3) + + >>> mock.second() + + >>> int(mock) + 1 + >>> result(1) + + >>> expected = [call(1, 2, 3), call.first(a=3), call.second(), + ... call.__int__(), call()(1)] + >>> mock.mock_calls == expected + True + + Members of `mock_calls` are :data:`call` objects. These can be + unpacked as tuples to get at the individual arguments. See + :ref:`calls as tuples `. + + + .. attribute:: __class__ + + Normally the `__class__` attribute of an object will return its type. + For a mock object with a `spec` `__class__` returns the spec class + instead. This allows mock objects to pass `isinstance` tests for the + object they are replacing / masquerading as: + + .. doctest:: + + >>> mock = Mock(spec=3) + >>> isinstance(mock, int) + True + + `__class__` is assignable to, this allows a mock to pass an + `isinstance` check without forcing you to use a spec: + + .. doctest:: + + >>> mock = Mock() + >>> mock.__class__ = dict + >>> isinstance(mock, dict) + True + +.. class:: NonCallableMock(spec=None, wraps=None, name=None, spec_set=None, **kwargs) + + A non-callable version of `Mock`. The constructor parameters have the same + meaning of `Mock`, with the exception of `return_value` and `side_effect` + which have no meaning on a non-callable mock. + +Mock objects that use a class or an instance as a `spec` or `spec_set` are able +to pass `isintance` tests: + +.. doctest:: + + >>> mock = Mock(spec=SomeClass) + >>> isinstance(mock, SomeClass) + True + >>> mock = Mock(spec_set=SomeClass()) + >>> isinstance(mock, SomeClass) + True + +The `Mock` classes have support for mocking magic methods. See :ref:`magic +methods ` for the full details. + +The mock classes and the :func:`patch` decorators all take arbitrary keyword +arguments for configuration. For the `patch` decorators the keywords are +passed to the constructor of the mock being created. The keyword arguments +are for configuring attributes of the mock: + +.. doctest:: + + >>> m = MagicMock(attribute=3, other='fish') + >>> m.attribute + 3 + >>> m.other + 'fish' + +The return value and side effect of child mocks can be set in the same way, +using dotted notation. As you can't use dotted names directly in a call you +have to create a dictionary and unpack it using `**`: + +.. doctest:: + + >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} + >>> mock = Mock(some_attribute='eggs', **attrs) + >>> mock.some_attribute + 'eggs' + >>> mock.method() + 3 + >>> mock.other() + Traceback (most recent call last): + ... + KeyError + + +.. class:: PropertyMock(*args, **kwargs) + + A mock intended to be used as a property, or other descriptor, on a class. + `PropertyMock` provides `__get__` and `__set__` methods so you can specify + a return value when it is fetched. + + Fetching a `PropertyMock` instance from an object calls the mock, with + no args. Setting it calls the mock with the value being set. + + .. doctest:: + + >>> class Foo(object): + ... @property + ... def foo(self): + ... return 'something' + ... @foo.setter + ... def foo(self, value): + ... pass + ... + >>> with patch('__main__.Foo.foo', new_callable=PropertyMock) as mock_foo: + ... mock_foo.return_value = 'mockity-mock' + ... this_foo = Foo() + ... print this_foo.foo + ... this_foo.foo = 6 + ... + mockity-mock + >>> mock_foo.mock_calls + [call(), call(6)] + +Because of the way mock attributes are stored you can't directly attach a +`PropertyMock` to a mock object. Instead you can attach it to the mock type +object: + +.. doctest:: + + >>> m = MagicMock() + >>> p = PropertyMock(return_value=3) + >>> type(m).foo = p + >>> m.foo + 3 + >>> p.assert_called_once_with() + + +.. index:: __call__ +.. index:: calling + +Calling +======= + +Mock objects are callable. The call will return the value set as the +:attr:`~Mock.return_value` attribute. The default return value is a new Mock +object; it is created the first time the return value is accessed (either +explicitly or by calling the Mock) - but it is stored and the same one +returned each time. + +Calls made to the object will be recorded in the attributes +like :attr:`~Mock.call_args` and :attr:`~Mock.call_args_list`. + +If :attr:`~Mock.side_effect` is set then it will be called after the call has +been recorded, so if `side_effect` raises an exception the call is still +recorded. + +The simplest way to make a mock raise an exception when called is to make +:attr:`~Mock.side_effect` an exception class or instance: + +.. doctest:: + + >>> m = MagicMock(side_effect=IndexError) + >>> m(1, 2, 3) + Traceback (most recent call last): + ... + IndexError + >>> m.mock_calls + [call(1, 2, 3)] + >>> m.side_effect = KeyError('Bang!') + >>> m('two', 'three', 'four') + Traceback (most recent call last): + ... + KeyError: 'Bang!' + >>> m.mock_calls + [call(1, 2, 3), call('two', 'three', 'four')] + +If `side_effect` is a function then whatever that function returns is what +calls to the mock return. The `side_effect` function is called with the +same arguments as the mock. This allows you to vary the return value of the +call dynamically, based on the input: + +.. doctest:: + + >>> def side_effect(value): + ... return value + 1 + ... + >>> m = MagicMock(side_effect=side_effect) + >>> m(1) + 2 + >>> m(2) + 3 + >>> m.mock_calls + [call(1), call(2)] + +If you want the mock to still return the default return value (a new mock), or +any set return value, then there are two ways of doing this. Either return +`mock.return_value` from inside `side_effect`, or return :data:`DEFAULT`: + +.. doctest:: + + >>> m = MagicMock() + >>> def side_effect(*args, **kwargs): + ... return m.return_value + ... + >>> m.side_effect = side_effect + >>> m.return_value = 3 + >>> m() + 3 + >>> def side_effect(*args, **kwargs): + ... return DEFAULT + ... + >>> m.side_effect = side_effect + >>> m() + 3 + +To remove a `side_effect`, and return to the default behaviour, set the +`side_effect` to `None`: + +.. doctest:: + + >>> m = MagicMock(return_value=6) + >>> def side_effect(*args, **kwargs): + ... return 3 + ... + >>> m.side_effect = side_effect + >>> m() + 3 + >>> m.side_effect = None + >>> m() + 6 + +The `side_effect` can also be any iterable object. Repeated calls to the mock +will return values from the iterable (until the iterable is exhausted and +a `StopIteration` is raised): + +.. doctest:: + + >>> m = MagicMock(side_effect=[1, 2, 3]) + >>> m() + 1 + >>> m() + 2 + >>> m() + 3 + >>> m() + Traceback (most recent call last): + ... + StopIteration + +If any members of the iterable are exceptions they will be raised instead of +returned: + +.. doctest:: + + >>> iterable = (33, ValueError, 66) + >>> m = MagicMock(side_effect=iterable) + >>> m() + 33 + >>> m() + Traceback (most recent call last): + ... + ValueError + >>> m() + 66 + + +.. _deleting-attributes: + +Deleting Attributes +=================== + +Mock objects create attributes on demand. This allows them to pretend to be +objects of any type. + +You may want a mock object to return `False` to a `hasattr` call, or raise an +`AttributeError` when an attribute is fetched. You can do this by providing +an object as a `spec` for a mock, but that isn't always convenient. + +You "block" attributes by deleting them. Once deleted, accessing an attribute +will raise an `AttributeError`. + +.. doctest:: + + >>> mock = MagicMock() + >>> hasattr(mock, 'm') + True + >>> del mock.m + >>> hasattr(mock, 'm') + False + >>> del mock.f + >>> mock.f + Traceback (most recent call last): + ... + AttributeError: f + + +Attaching Mocks as Attributes +============================= + +When you attach a mock as an attribute of another mock (or as the return +value) it becomes a "child" of that mock. Calls to the child are recorded in +the :attr:`~Mock.method_calls` and :attr:`~Mock.mock_calls` attributes of the +parent. This is useful for configuring child mocks and then attaching them to +the parent, or for attaching mocks to a parent that records all calls to the +children and allows you to make assertions about the order of calls between +mocks: + +.. doctest:: + + >>> parent = MagicMock() + >>> child1 = MagicMock(return_value=None) + >>> child2 = MagicMock(return_value=None) + >>> parent.child1 = child1 + >>> parent.child2 = child2 + >>> child1(1) + >>> child2(2) + >>> parent.mock_calls + [call.child1(1), call.child2(2)] + +The exception to this is if the mock has a name. This allows you to prevent +the "parenting" if for some reason you don't want it to happen. + +.. doctest:: + + >>> mock = MagicMock() + >>> not_a_child = MagicMock(name='not-a-child') + >>> mock.attribute = not_a_child + >>> mock.attribute() + + >>> mock.mock_calls + [] + +Mocks created for you by :func:`patch` are automatically given names. To +attach mocks that have names to a parent you use the :meth:`~Mock.attach_mock` +method: + +.. doctest:: + + >>> thing1 = object() + >>> thing2 = object() + >>> parent = MagicMock() + >>> with patch('__main__.thing1', return_value=None) as child1: + ... with patch('__main__.thing2', return_value=None) as child2: + ... parent.attach_mock(child1, 'child1') + ... parent.attach_mock(child2, 'child2') + ... child1('one') + ... child2('two') + ... + >>> parent.mock_calls + [call.child1('one'), call.child2('two')] + + +----- + +.. [#] The only exceptions are magic methods and attributes (those that have + leading and trailing double underscores). Mock doesn't create these but + instead of raises an ``AttributeError``. This is because the interpreter + will often implicitly request these methods, and gets *very* confused to + get a new Mock object when it expects a magic method. If you need magic + method support see :ref:`magic methods `. diff --git a/python/mock-1.0.0/docs/patch.txt b/python/mock-1.0.0/docs/patch.txt new file mode 100644 index 00000000000..3d56264fbbf --- /dev/null +++ b/python/mock-1.0.0/docs/patch.txt @@ -0,0 +1,636 @@ +================== + Patch Decorators +================== + + +.. currentmodule:: mock + +.. testsetup:: + + class SomeClass(object): + static_method = None + class_method = None + attribute = None + + sys.modules['package'] = package = Mock(name='package') + sys.modules['package.module'] = package.module + + class TestCase(unittest2.TestCase): + def run(self): + result = unittest2.TestResult() + super(unittest2.TestCase, self).run(result) + assert result.wasSuccessful() + +.. testcleanup:: + + patch.TEST_PREFIX = 'test' + + +The patch decorators are used for patching objects only within the scope of +the function they decorate. They automatically handle the unpatching for you, +even if exceptions are raised. All of these functions can also be used in with +statements or as class decorators. + + +patch +===== + +.. note:: + + `patch` is straightforward to use. The key is to do the patching in the + right namespace. See the section `where to patch`_. + +.. function:: patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs) + + `patch` acts as a function decorator, class decorator or a context + manager. Inside the body of the function or with statement, the `target` + is patched with a `new` object. When the function/with statement exits + the patch is undone. + + If `new` is omitted, then the target is replaced with a + :class:`MagicMock`. If `patch` is used as a decorator and `new` is + omitted, the created mock is passed in as an extra argument to the + decorated function. If `patch` is used as a context manager the created + mock is returned by the context manager. + + `target` should be a string in the form `'package.module.ClassName'`. The + `target` is imported and the specified object replaced with the `new` + object, so the `target` must be importable from the environment you are + calling `patch` from. The target is imported when the decorated function + is executed, not at decoration time. + + The `spec` and `spec_set` keyword arguments are passed to the `MagicMock` + if patch is creating one for you. + + In addition you can pass `spec=True` or `spec_set=True`, which causes + patch to pass in the object being mocked as the spec/spec_set object. + + `new_callable` allows you to specify a different class, or callable object, + that will be called to create the `new` object. By default `MagicMock` is + used. + + A more powerful form of `spec` is `autospec`. If you set `autospec=True` + then the mock with be created with a spec from the object being replaced. + All attributes of the mock will also have the spec of the corresponding + attribute of the object being replaced. Methods and functions being mocked + will have their arguments checked and will raise a `TypeError` if they are + called with the wrong signature. For mocks + replacing a class, their return value (the 'instance') will have the same + spec as the class. See the :func:`create_autospec` function and + :ref:`auto-speccing`. + + Instead of `autospec=True` you can pass `autospec=some_object` to use an + arbitrary object as the spec instead of the one being replaced. + + By default `patch` will fail to replace attributes that don't exist. If + you pass in `create=True`, and the attribute doesn't exist, patch will + create the attribute for you when the patched function is called, and + delete it again afterwards. This is useful for writing tests against + attributes that your production code creates at runtime. It is off by by + default because it can be dangerous. With it switched on you can write + passing tests against APIs that don't actually exist! + + Patch can be used as a `TestCase` class decorator. It works by + decorating each test method in the class. This reduces the boilerplate + code when your test methods share a common patchings set. `patch` finds + tests by looking for method names that start with `patch.TEST_PREFIX`. + By default this is `test`, which matches the way `unittest` finds tests. + You can specify an alternative prefix by setting `patch.TEST_PREFIX`. + + Patch can be used as a context manager, with the with statement. Here the + patching applies to the indented block after the with statement. If you + use "as" then the patched object will be bound to the name after the + "as"; very useful if `patch` is creating a mock object for you. + + `patch` takes arbitrary keyword arguments. These will be passed to + the `Mock` (or `new_callable`) on construction. + + `patch.dict(...)`, `patch.multiple(...)` and `patch.object(...)` are + available for alternate use-cases. + +`patch` as function decorator, creating the mock for you and passing it into +the decorated function: + +.. doctest:: + + >>> @patch('__main__.SomeClass') + ... def function(normal_argument, mock_class): + ... print mock_class is SomeClass + ... + >>> function(None) + True + + +Patching a class replaces the class with a `MagicMock` *instance*. If the +class is instantiated in the code under test then it will be the +:attr:`~Mock.return_value` of the mock that will be used. + +If the class is instantiated multiple times you could use +:attr:`~Mock.side_effect` to return a new mock each time. Alternatively you +can set the `return_value` to be anything you want. + +To configure return values on methods of *instances* on the patched class +you must do this on the `return_value`. For example: + +.. doctest:: + + >>> class Class(object): + ... def method(self): + ... pass + ... + >>> with patch('__main__.Class') as MockClass: + ... instance = MockClass.return_value + ... instance.method.return_value = 'foo' + ... assert Class() is instance + ... assert Class().method() == 'foo' + ... + +If you use `spec` or `spec_set` and `patch` is replacing a *class*, then the +return value of the created mock will have the same spec. + +.. doctest:: + + >>> Original = Class + >>> patcher = patch('__main__.Class', spec=True) + >>> MockClass = patcher.start() + >>> instance = MockClass() + >>> assert isinstance(instance, Original) + >>> patcher.stop() + +The `new_callable` argument is useful where you want to use an alternative +class to the default :class:`MagicMock` for the created mock. For example, if +you wanted a :class:`NonCallableMock` to be used: + +.. doctest:: + + >>> thing = object() + >>> with patch('__main__.thing', new_callable=NonCallableMock) as mock_thing: + ... assert thing is mock_thing + ... thing() + ... + Traceback (most recent call last): + ... + TypeError: 'NonCallableMock' object is not callable + +Another use case might be to replace an object with a `StringIO` instance: + +.. doctest:: + + >>> from StringIO import StringIO + >>> def foo(): + ... print 'Something' + ... + >>> @patch('sys.stdout', new_callable=StringIO) + ... def test(mock_stdout): + ... foo() + ... assert mock_stdout.getvalue() == 'Something\n' + ... + >>> test() + +When `patch` is creating a mock for you, it is common that the first thing +you need to do is to configure the mock. Some of that configuration can be done +in the call to patch. Any arbitrary keywords you pass into the call will be +used to set attributes on the created mock: + +.. doctest:: + + >>> patcher = patch('__main__.thing', first='one', second='two') + >>> mock_thing = patcher.start() + >>> mock_thing.first + 'one' + >>> mock_thing.second + 'two' + +As well as attributes on the created mock attributes, like the +:attr:`~Mock.return_value` and :attr:`~Mock.side_effect`, of child mocks can +also be configured. These aren't syntactically valid to pass in directly as +keyword arguments, but a dictionary with these as keys can still be expanded +into a `patch` call using `**`: + +.. doctest:: + + >>> config = {'method.return_value': 3, 'other.side_effect': KeyError} + >>> patcher = patch('__main__.thing', **config) + >>> mock_thing = patcher.start() + >>> mock_thing.method() + 3 + >>> mock_thing.other() + Traceback (most recent call last): + ... + KeyError + + +patch.object +============ + +.. function:: patch.object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs) + + patch the named member (`attribute`) on an object (`target`) with a mock + object. + + `patch.object` can be used as a decorator, class decorator or a context + manager. Arguments `new`, `spec`, `create`, `spec_set`, `autospec` and + `new_callable` have the same meaning as for `patch`. Like `patch`, + `patch.object` takes arbitrary keyword arguments for configuring the mock + object it creates. + + When used as a class decorator `patch.object` honours `patch.TEST_PREFIX` + for choosing which methods to wrap. + +You can either call `patch.object` with three arguments or two arguments. The +three argument form takes the object to be patched, the attribute name and the +object to replace the attribute with. + +When calling with the two argument form you omit the replacement object, and a +mock is created for you and passed in as an extra argument to the decorated +function: + +.. doctest:: + + >>> @patch.object(SomeClass, 'class_method') + ... def test(mock_method): + ... SomeClass.class_method(3) + ... mock_method.assert_called_with(3) + ... + >>> test() + +`spec`, `create` and the other arguments to `patch.object` have the same +meaning as they do for `patch`. + + +patch.dict +========== + +.. function:: patch.dict(in_dict, values=(), clear=False, **kwargs) + + Patch a dictionary, or dictionary like object, and restore the dictionary + to its original state after the test. + + `in_dict` can be a dictionary or a mapping like container. If it is a + mapping then it must at least support getting, setting and deleting items + plus iterating over keys. + + `in_dict` can also be a string specifying the name of the dictionary, which + will then be fetched by importing it. + + `values` can be a dictionary of values to set in the dictionary. `values` + can also be an iterable of `(key, value)` pairs. + + If `clear` is True then the dictionary will be cleared before the new + values are set. + + `patch.dict` can also be called with arbitrary keyword arguments to set + values in the dictionary. + + `patch.dict` can be used as a context manager, decorator or class + decorator. When used as a class decorator `patch.dict` honours + `patch.TEST_PREFIX` for choosing which methods to wrap. + +`patch.dict` can be used to add members to a dictionary, or simply let a test +change a dictionary, and ensure the dictionary is restored when the test +ends. + +.. doctest:: + + >>> from mock import patch + >>> foo = {} + >>> with patch.dict(foo, {'newkey': 'newvalue'}): + ... assert foo == {'newkey': 'newvalue'} + ... + >>> assert foo == {} + + >>> import os + >>> with patch.dict('os.environ', {'newkey': 'newvalue'}): + ... print os.environ['newkey'] + ... + newvalue + >>> assert 'newkey' not in os.environ + +Keywords can be used in the `patch.dict` call to set values in the dictionary: + +.. doctest:: + + >>> mymodule = MagicMock() + >>> mymodule.function.return_value = 'fish' + >>> with patch.dict('sys.modules', mymodule=mymodule): + ... import mymodule + ... mymodule.function('some', 'args') + ... + 'fish' + +`patch.dict` can be used with dictionary like objects that aren't actually +dictionaries. At the very minimum they must support item getting, setting, +deleting and either iteration or membership test. This corresponds to the +magic methods `__getitem__`, `__setitem__`, `__delitem__` and either +`__iter__` or `__contains__`. + +.. doctest:: + + >>> class Container(object): + ... def __init__(self): + ... self.values = {} + ... def __getitem__(self, name): + ... return self.values[name] + ... def __setitem__(self, name, value): + ... self.values[name] = value + ... def __delitem__(self, name): + ... del self.values[name] + ... def __iter__(self): + ... return iter(self.values) + ... + >>> thing = Container() + >>> thing['one'] = 1 + >>> with patch.dict(thing, one=2, two=3): + ... assert thing['one'] == 2 + ... assert thing['two'] == 3 + ... + >>> assert thing['one'] == 1 + >>> assert list(thing) == ['one'] + + +patch.multiple +============== + +.. function:: patch.multiple(target, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs) + + Perform multiple patches in a single call. It takes the object to be + patched (either as an object or a string to fetch the object by importing) + and keyword arguments for the patches:: + + with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'): + ... + + Use :data:`DEFAULT` as the value if you want `patch.multiple` to create + mocks for you. In this case the created mocks are passed into a decorated + function by keyword, and a dictionary is returned when `patch.multiple` is + used as a context manager. + + `patch.multiple` can be used as a decorator, class decorator or a context + manager. The arguments `spec`, `spec_set`, `create`, `autospec` and + `new_callable` have the same meaning as for `patch`. These arguments will + be applied to *all* patches done by `patch.multiple`. + + When used as a class decorator `patch.multiple` honours `patch.TEST_PREFIX` + for choosing which methods to wrap. + +If you want `patch.multiple` to create mocks for you, then you can use +:data:`DEFAULT` as the value. If you use `patch.multiple` as a decorator +then the created mocks are passed into the decorated function by keyword. + +.. doctest:: + + >>> thing = object() + >>> other = object() + + >>> @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) + ... def test_function(thing, other): + ... assert isinstance(thing, MagicMock) + ... assert isinstance(other, MagicMock) + ... + >>> test_function() + +`patch.multiple` can be nested with other `patch` decorators, but put arguments +passed by keyword *after* any of the standard arguments created by `patch`: + +.. doctest:: + + >>> @patch('sys.exit') + ... @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) + ... def test_function(mock_exit, other, thing): + ... assert 'other' in repr(other) + ... assert 'thing' in repr(thing) + ... assert 'exit' in repr(mock_exit) + ... + >>> test_function() + +If `patch.multiple` is used as a context manager, the value returned by the +context manger is a dictionary where created mocks are keyed by name: + +.. doctest:: + + >>> with patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) as values: + ... assert 'other' in repr(values['other']) + ... assert 'thing' in repr(values['thing']) + ... assert values['thing'] is thing + ... assert values['other'] is other + ... + + +.. _start-and-stop: + +patch methods: start and stop +============================= + +All the patchers have `start` and `stop` methods. These make it simpler to do +patching in `setUp` methods or where you want to do multiple patches without +nesting decorators or with statements. + +To use them call `patch`, `patch.object` or `patch.dict` as normal and keep a +reference to the returned `patcher` object. You can then call `start` to put +the patch in place and `stop` to undo it. + +If you are using `patch` to create a mock for you then it will be returned by +the call to `patcher.start`. + +.. doctest:: + + >>> patcher = patch('package.module.ClassName') + >>> from package import module + >>> original = module.ClassName + >>> new_mock = patcher.start() + >>> assert module.ClassName is not original + >>> assert module.ClassName is new_mock + >>> patcher.stop() + >>> assert module.ClassName is original + >>> assert module.ClassName is not new_mock + + +A typical use case for this might be for doing multiple patches in the `setUp` +method of a `TestCase`: + +.. doctest:: + + >>> class MyTest(TestCase): + ... def setUp(self): + ... self.patcher1 = patch('package.module.Class1') + ... self.patcher2 = patch('package.module.Class2') + ... self.MockClass1 = self.patcher1.start() + ... self.MockClass2 = self.patcher2.start() + ... + ... def tearDown(self): + ... self.patcher1.stop() + ... self.patcher2.stop() + ... + ... def test_something(self): + ... assert package.module.Class1 is self.MockClass1 + ... assert package.module.Class2 is self.MockClass2 + ... + >>> MyTest('test_something').run() + +.. caution:: + + If you use this technique you must ensure that the patching is "undone" by + calling `stop`. This can be fiddlier than you might think, because if an + exception is raised in the setUp then tearDown is not called. `unittest2 + `_ cleanup functions make this + easier. + + .. doctest:: + + >>> class MyTest(TestCase): + ... def setUp(self): + ... patcher = patch('package.module.Class') + ... self.MockClass = patcher.start() + ... self.addCleanup(patcher.stop) + ... + ... def test_something(self): + ... assert package.module.Class is self.MockClass + ... + >>> MyTest('test_something').run() + + As an added bonus you no longer need to keep a reference to the `patcher` + object. + +It is also possible to stop all patches which have been started by using +`patch.stopall`. + +.. function:: patch.stopall + + Stop all active patches. Only stops patches started with `start`. + + +TEST_PREFIX +=========== + +All of the patchers can be used as class decorators. When used in this way +they wrap every test method on the class. The patchers recognise methods that +start with `test` as being test methods. This is the same way that the +`unittest.TestLoader` finds test methods by default. + +It is possible that you want to use a different prefix for your tests. You can +inform the patchers of the different prefix by setting `patch.TEST_PREFIX`: + +.. doctest:: + + >>> patch.TEST_PREFIX = 'foo' + >>> value = 3 + >>> + >>> @patch('__main__.value', 'not three') + ... class Thing(object): + ... def foo_one(self): + ... print value + ... def foo_two(self): + ... print value + ... + >>> + >>> Thing().foo_one() + not three + >>> Thing().foo_two() + not three + >>> value + 3 + + +Nesting Patch Decorators +======================== + +If you want to perform multiple patches then you can simply stack up the +decorators. + +You can stack up multiple patch decorators using this pattern: + +.. doctest:: + + >>> @patch.object(SomeClass, 'class_method') + ... @patch.object(SomeClass, 'static_method') + ... def test(mock1, mock2): + ... assert SomeClass.static_method is mock1 + ... assert SomeClass.class_method is mock2 + ... SomeClass.static_method('foo') + ... SomeClass.class_method('bar') + ... return mock1, mock2 + ... + >>> mock1, mock2 = test() + >>> mock1.assert_called_once_with('foo') + >>> mock2.assert_called_once_with('bar') + + +Note that the decorators are applied from the bottom upwards. This is the +standard way that Python applies decorators. The order of the created mocks +passed into your test function matches this order. + +Like all context-managers patches can be nested using contextlib's nested +function; *every* patching will appear in the tuple after "as": + +.. doctest:: + + >>> from contextlib import nested + >>> with nested( + ... patch('package.module.ClassName1'), + ... patch('package.module.ClassName2') + ... ) as (MockClass1, MockClass2): + ... assert package.module.ClassName1 is MockClass1 + ... assert package.module.ClassName2 is MockClass2 + ... + + +.. _where-to-patch: + +Where to patch +============== + +`patch` works by (temporarily) changing the object that a *name* points to with +another one. There can be many names pointing to any individual object, so +for patching to work you must ensure that you patch the name used by the system +under test. + +The basic principle is that you patch where an object is *looked up*, which +is not necessarily the same place as where it is defined. A couple of +examples will help to clarify this. + +Imagine we have a project that we want to test with the following structure:: + + a.py + -> Defines SomeClass + + b.py + -> from a import SomeClass + -> some_function instantiates SomeClass + +Now we want to test `some_function` but we want to mock out `SomeClass` using +`patch`. The problem is that when we import module b, which we will have to +do then it imports `SomeClass` from module a. If we use `patch` to mock out +`a.SomeClass` then it will have no effect on our test; module b already has a +reference to the *real* `SomeClass` and it looks like our patching had no +effect. + +The key is to patch out `SomeClass` where it is used (or where it is looked up +). In this case `some_function` will actually look up `SomeClass` in module b, +where we have imported it. The patching should look like: + + `@patch('b.SomeClass')` + +However, consider the alternative scenario where instead of `from a import +SomeClass` module b does `import a` and `some_function` uses `a.SomeClass`. Both +of these import forms are common. In this case the class we want to patch is +being looked up on the a module and so we have to patch `a.SomeClass` instead: + + `@patch('a.SomeClass')` + + +Patching Descriptors and Proxy Objects +====================================== + +Since version 0.6.0 both patch_ and patch.object_ have been able to correctly +patch and restore descriptors: class methods, static methods and properties. +You should patch these on the *class* rather than an instance. + +Since version 0.7.0 patch_ and patch.object_ work correctly with some objects +that proxy attribute access, like the `django setttings object +`_. + +.. note:: + + In django `import settings` and `from django.conf import settings` + return different objects. If you are using libraries / apps that do both you + may have to patch both. Grrr... diff --git a/python/mock-1.0.0/docs/sentinel.txt b/python/mock-1.0.0/docs/sentinel.txt new file mode 100644 index 00000000000..1c5223da0ed --- /dev/null +++ b/python/mock-1.0.0/docs/sentinel.txt @@ -0,0 +1,58 @@ +========== + Sentinel +========== + + +.. currentmodule:: mock + +.. testsetup:: + + class ProductionClass(object): + def something(self): + return self.method() + + class Test(unittest2.TestCase): + def testSomething(self): + pass + self = Test('testSomething') + + +.. data:: sentinel + + The ``sentinel`` object provides a convenient way of providing unique + objects for your tests. + + Attributes are created on demand when you access them by name. Accessing + the same attribute will always return the same object. The objects + returned have a sensible repr so that test failure messages are readable. + + +.. data:: DEFAULT + + The `DEFAULT` object is a pre-created sentinel (actually + `sentinel.DEFAULT`). It can be used by :attr:`~Mock.side_effect` + functions to indicate that the normal return value should be used. + + +Sentinel Example +================ + +Sometimes when testing you need to test that a specific object is passed as an +argument to another method, or returned. It can be common to create named +sentinel objects to test this. `sentinel` provides a convenient way of +creating and testing the identity of objects like this. + +In this example we monkey patch `method` to return +`sentinel.some_object`: + +.. doctest:: + + >>> real = ProductionClass() + >>> real.method = Mock(name="method") + >>> real.method.return_value = sentinel.some_object + >>> result = real.method() + >>> assert result is sentinel.some_object + >>> sentinel.some_object + sentinel.some_object + + diff --git a/python/mock-1.0.0/html/.doctrees/changelog.doctree b/python/mock-1.0.0/html/.doctrees/changelog.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6356303021b5794bac29edb8ff73cd900b6368dc GIT binary patch literal 282659 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 Z;|_tq4qHMyT2z&P7(X}#St)Bv{~xSB+hG6z literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/.doctrees/compare.doctree b/python/mock-1.0.0/html/.doctrees/compare.doctree new file mode 100644 index 0000000000000000000000000000000000000000..2a961fce4ac230fdfc1efb36afebae3a1327655c GIT binary patch literal 56915 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 literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/.doctrees/examples.doctree b/python/mock-1.0.0/html/.doctrees/examples.doctree new file mode 100644 index 0000000000000000000000000000000000000000..40e4fded532692cace88441947b318afc91f36d9 GIT binary patch literal 167478 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 literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/.doctrees/getting-started.doctree b/python/mock-1.0.0/html/.doctrees/getting-started.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ba82a899840d5582d02f280cd38fdeed04cd2e43 GIT binary patch literal 70942 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%0AW%?PDMLwSD%amw_(qWM81>_T!12 zsFm%O)_udO8cOX~&F$~~v!@}mJ^CoF;RI!>kfXfi^VZetO0{&zIs!B3pnpffqsZoG z<$C*j^5d9_wFq9Dl_t&3MnlYs74j7GVWpXx0afAe%^ghDz%;K5rgNv221tScK}u29 zX?FlL#^zA4Vtt^|jS>%%CrX?uM7duQqU?4dO6>rpwsk|b<#14kQZu9<$+4S8NCepl z)LfTEOV2~JJRy;`eMlN)|qc|(+P$)W&kx;;HmaPozOagyCOa^`fdvW z-v`S9zI%l5-7AFeJ|TRUKssMWhT4OExwmB(A?1W7T^VP1bU@8jc!tIzf-n|(Ws5h^ z?5gHPV!b5x>6*d-Swrq2K+VnMDQSKBWXvMxNX0{RGBy9*WJ!E z3aoePEfKym*ROCs4V5<)M#^lelJBb&<-_ce3Mqp;HMzkoNZNhb*bdv*%)(;R)#Qj} z9&QX`nO~6umU)B_%REwuWqwtNWgZ1mBBPGx(Q;RfbPO*w_cfl<>i@E!zAly~c1!cg zp4_p<8X`JQ9*F20LPT`D5D}dqL`2^NRcTFB$vu%Ds<2l3Sj%zWA}Gb8 ziSh?>W@PJr$V<)rh$p7jZOlOZ`SzRSeyngLcY!&~=@un*16_9^E*k5)ixd*g7w~4 z;!>}_pIgXZ1R=FHIk*yWSH;niO1xUJVkKU~&x8`MRU}iQW?Q2w#$AVdQj^y!TA;}r z6bB*wQi#xQRMr1Q(LT5*6a~RO zY5oZADLEjxr-i8O86nbnR)}<-10@~R8IsQP!ba)5fFsiR4N!A0@(iUTVoW;SReK}m z4wBA%X{LTllH~dlP;CCKkR*s<=Vn)F^x@kSkL ztsFDXEmkY3R&|nFI}X=?D(&4n;*PV#!BPfIaZ|@WZXn;0W(h`i)7xKiRmRypvr{wG zo?9o~cOKLO4-@qL>vo@RyB5Hip><8Ia}QIZPZ|XDUHSTfx;hIp85aZ`D8(l6H)y1u zcCVV0!oGsCL8f3;-(79^wf(4o}k-i{+CYtz3gUR;~L!%v$CmFC?) z6vrx47084YxOeeMR;TYNUQnI>Q?U@&`$EL`fx^~Rryt^C)#)&zNYV=kk?exPy6SX8 zmEMKqtm<@OUTSVQPgSSJ61Bnz`;Fp8Djdm;GKV?cqQu7A{j(<+L!Yu* z87~T|zK}4L<6?4e%Z`9;Z%u9tE+}nrpyrm~8CDjesi7*>0>_Z$#4YJlS;|sb+NWac zAwvgWh7ggq!>V#wi@aPAStR=6=ayG!RI*G= zP#XNPXyVdJ0m)AkqKWmu`ZBRTE>1p%5i)Bph5OHkPx>#3sDd+!uMOOc;CE zz^3*a8`w=u%U!#twoi++s;SY-XiVbBbngMcSjuRHNKMt+&KuSPRB=RJZ>-C zE^$gpwePBgVzuwa&sgodD~_psm7&!hrSB#aG^zV3N{Bz{eh(#sT=x{B`@O(=-M8Ul zy5CztP}V*|RFDx4*8RS6mhSiCrRJ28bZU4h)4yOC5 z3JHy05|VK{DBVlk(tU@}@8YS~eIx=5AV(k!U>Zq!Xb)Akq9XnQRtJ-$9DP~+W zL&;tbc5Y}TniU9zS1(F^m5vHqXqde0o6!-7r`2^EIrW{DgN+V!+#^qP+$+So`h;k* zBt(<_pgJp?hMko%4(hC==79s}2#iWAK+RPxW_sRY4iFd_N@}cv1-<-3ES;G_Ix9iJ z+-2luDFw`VwgrdPL*)RghY4Zz%R*SK31RhcP)4gfWwc)rwq;i!o4IMi9YGivJQAq6 zuks9SR-`sGaiW)Hh`DIa9p#fc+LAiPCzbt}IA0^!ASr#_QaCn9!Nx+dRk-661+jd? z{1MCXazHF62ocLSg^1-uA!7L!m=w!Na`$7QC|UPy9FfDxK+T=PGn9jfX^?}~)Fb0O z)kpk}Mf|RhXhYa2#A!H2JW`svx2L;7<@G&_^8FynmN;rX$94_dh{TU0QqgGEmV0b~ znrc*MwA+jonw0)ADH!D6QpxvBXCGSVBVReCBi>uqAD`M_d}_mSQ5eOJy%lRvx|OK26gOCjI$Q4;(m6s5>|7xRcAgM}`T-cNME#H-Q{9i`Gi$Khx`Cwoh#LP^DMkW?RMFmAuv5&+WJG z$g6v7d#@(@h4t-d-NN$-d)Rpd_X{OCv0Fq2oiS~Vb5~g^P<6FDpz0bSR9!2Cs_TT1 zbUi2&i|DyQ9)A2i583~c(1`IypyqDki5R;Bn#7Mu(k4xAwjj6oAlY}R_gEZJGfi_| z4cA39$zhxFnKg;F6Lb}|Ie;0Ex$i4rx}qsDT@lPu^vq_3B-llu4W)iJ8+`;;J2d}P zA}@B@Mu$7JzEVo%nu5Mm3!C0lWel+cAvM6hWoI3gaW*jvBYlZ|4w^n82bNX3nrsC% zo;jJBo1J#IB8Gac*;?mqCLOePyZq7G9YVBrrx30EN{H6(0;RQRY2|KtwEh1s)_afw z)_5;abNBI-H3qVl{l(UJzXf@~2gwd#jr(AV71~J_#Tq+O9W0M)MO*WFxl~7IPl?s+ zuuqvz?J_-3x1ZjyZ7r;e2Yc0I<9!Ms-%1dh**UqT-Gfjtgdsd+gkcB|%O6AdwGcyi zM2I0gD#Q>T17!$%ht+VEfjRWAP4CFjS#wD6hilJh0y&HsE(AfQvdRI!ZzEk-_Yi}m+^w~ z-vc%G3eT{n7GVwDDJ@^sDa*a;W4~sxU-z+X)xAF0AMhE(`5!IPpMps0s)f3Da}*wi z-!M-Yep3!G{AVEyza@m>zX)OYuV9klzsWtA6*GHsZ{q@=e+O#r9iAbdBBO!NO~j|K zraqzlxqtX<-nDGr^Vwwojr9IW5`!f7zNP*_kh-?%c*%9R4;3824O>VIBDe*3k>C~- zBDjTw2yS5^f*TGd1vf(OZNV~?3gSo{klQGr=0@`j_{)Zb&rWjGIFuy>urqP1b*OEU{Ut5Sa z))8X<%|f)$0!q$PLphJbK^6Q4{}LB&U7{ngF90<+-qK3{t7%OjI=o4`adVNjlqUu$ zCyV}i3Qo~q-~8Zx13AF^hC+DXNC@v63*mheP(@!EsFC=hFj@3B#Q|nE18T07XQ+M= z(NOd!s_1`0S~hh6+Ng)cF;Te+fj&KcM_u4 zorUOe7ci(qL0FneTwx&8QP|D5#bMl5xBKvg5r;5{~=yXGsVXlYmeOnslH}YE=o$H z5I@MWnr>!Mht-KyW&BfR5{8YKA2H=`i#$yhNr#NJ!FzhH*fYSobadD_?e<0(LsW44 zn2a!njQlZ%eT5jwenJX)R)}%z4@&(rLiHbjgDSY2DGmSY(sc*I40d%8P;*m_v-JNB z&b|aQ=v^fuSUqnyCOZNqQ>gFbjLYTXM3PHM)09TMl+?-3cqyq%aXxkhF;A%ED$;bN zL5EPQNHdfICRh++l0}8BTUGDI#a59HRuHVWM~Jod3hP#pW~gQAle1QlO1#uuKTj%< zZex!YuWY|53{K%lu3`>zx>{NKDL~H*(X@D1P}KK+VaK6#{3F_BTJ$23n$i&u%wRk zNon7D{lwZ=%>$twrT7T#Xd&u4Mu=#>CS1)X*1oRSQ8uyG7uF-k;^rsTj^m96CC!ncHwaFP)1d>d3H7G-|2Jp6dJ zF|SkbLNliVHTNB!vXp=TQ5v^4-?bp8`5@VqX>GP)1I|$MSZeHPOqgWQ;+UF^J`Rfa zRf|;hUE0~J%P5FTLln~~jKf<3X_k5t{ ze$11ClwBHS4Pkul0{k`jxX{934j0J*bGTTDIb0&d94-}N4wr$da7BeG+@A>jIn_b7 z?S6_ULcbiSxu5Y2Yg-X#wQXipNVvPg+(@e(TL06f;z}}uvY!Jr_Y0m9(x;K}Rpb>Z zQB}S+{$FimToaJ7IyG9b2oRi$2qxtik6U+|4rg!;lwZtm4}W%0Cur0i*K(Cq$Bv~^ zhn+|AGp*73fuCxn_R$7P+a8eWunMmutkwLH318P)GX7{HD*+lrSxoQjGvx4;mJh5} z(h9cfqohqJmL_&IwLrkmQs4CA47$x-s@FK0U7S8UtQxK2uG?hjE4p?&M%R&c*LpD~ zS|+R2t}_v0#@EXqGrmEH8UIp<8Q&o&q^B#yIyCJStJ(cZ(I~ceSs3`eTMqDhj}U(E6~ga* zLioKORIyb?Dz*;@+ib?SE@}559S7 zSPe8(;K%SEB!R~*>JvfK;gW#N*gdJh@coqe!1vQ~fbVC7@cpb1zMm7q_w!(q?-%6W zmgPB&1l@0Vf!h~>n)@x!kXsSZz-=>e=CK^PmwdXvvvgnf>Dmou^P0f#i8DwluUK-g z2FckRk}t6CH3dg5ubUro`GXvg%O8cvqf5rjHyam+U zUwDR+5fKfN(T?Du>J0Z+pUB@Vk+*#!w!3f0(0?buAfDf`xc>;^#+LA|0>klp<^#w7 zlmi^UFNEU{gmCk>kET@p<4x|G~w zt6!R*@VX4Zy#_o(UPV9yultGD|4jYvmh*HhZ*;8S>B#;kGhC58BB|T<-jyuxm4m#s zm1^#-j$0*;o*b61s@UzYE;yq3b&4anEV#RlZ-Cv^%N2s*B6rU2B1t&f>n+i3fr=a!h9Xy8{v)(ZVYha z0?*I}MVZ-PJP@ZpG%QWvFIwPDec-IQM%qlw9&cvBT7zI~5>E4_;f|}Q6>UkZEJX%B zNgWZc087(4__ZbN=Ayc?%PGTP!6r)|z&2*tYKYaGEzNNX&Fq4x*x{FWcUYJPGuhUS zN=hZ#9u@`)GO9BfA~wgJtmY_=-!_PEU4x!r!td^a7weo&s!zskj=YfO5vnfjcD`uh zdkaajVd8rdZgmsiTPk`m@x7JavDdAISkpE_tZ7>zHnJTUO?+?9kEw14dHIR&9eGvP z?9i}lwv+tYvY#t5++g_8irg7-QCM~XYHnAaVaZatpk&1}O}kl;-QytjGfk7t1N}|m zMf%%Ah+g*;Qs(y(u5B|-ZF*hYW}1!-^|m)|ex_+3-fAvmzx|CxQyQ;>b^9uXiQS?p zIoGtGMS=)bsUl*3Aw(P?gop!$7}Y_b%qGfysyzJ2bZ~B8!V@9212xydQ`KNVf~Yk) z+1M;+L8kd2*{7+BcG0GlKE`C)$TWRG8>>?@iY#;3Y?2DK_QU9Q4?jlIn+7};Zg*gN zi(h%r&7cgBY)7_@c5LO;CtJw(^ef$&nuk(R_W}C*^V~{n321>fTyzL8skO|S!pw3# z476Bf>Ceru$u4MA#qICY<>W)K+QPOue}#;)Xmy>iS+4{eR&^QoC?PL@lrUY05@rZd zLP3ZUilCJ6jnD?V<=780nkQ3n za?7q1IjMPisT5o-qX2_%kCA_Taf~%Z3K+B-LxvCh646{g#MG%hs{6|#v!U*HxYgDD z6-5u~{;J-wi~%7QbBGX&nJL6VW`R-NKbs$Hx|f%)`w!z)_H$xrKVOz#+h;>cHE2Q0 zhXXbD6`o3c1LlL2!FC6YS7mFJ3>vZfqgm)8&tN&k!QsGlhuvEFt1O8v$Z|P9$VJCu=B5z$<2Z71 zFm-|A#)GK~`KgwFn2kCvQXCshX;N&mRdkV#-NpDOhfR@q13fF*ih;^g@nfIg=BmKsG*dEsvLhQ^jEgVmhEoD75&}>)ZEQH!&*snQPv6@ z#{;PYkXmfyj0EOyNw~$6a;uSYn+Wfbo7<>s6^;zoUvGSB)})O&@iTO4gCZpFe~Z(N_1tuzrqaXY$8k#K!~M22u6+i zL;PTew!D0!{%c-U!JQpeaF58ZE&KnYQXYkMO6y}l%{|UDEUmKFxMh68{Gasx*>Ox^ zu$x|s!~WVhn!)hqxKz$Aj_Zgmg|<$Y&#~ERppTn#IU~Yius7~XldT`w)pD|*d{?xn zow$sS=}MYL4KLKDGH~I4Fw&Wp@672^EM_%k=6iaYwDaFnVr$yLqO_9sYuQo$alXB| zuKakv&`q0ejjC-JrB(NPMrmJls|TJ68jY9iZcQ3)&X=Y&+d&kHf;7eJZog`v6r1_!mrt5b=5j2hgFghju< z1#0dki&kLeR^W%4YdvwKBxXlq`z0I>F&9ANErA*}sD2y1^7 z!rGrewJ6F(Ey^6>pcdr~T;btOpyvL}Gqh2WV=YR&1oVNmDF0vE+gn~-e=%|W)r%|p zxm?`e5JhCs;Y#=0CZ)dzQra8q*`HcXRf^_0TDx-9Tx*94GDm^zB%dZ%SSjWvQ;xZ( zbiK2`cAX7B+<>d~V$}pq$#T)^-tk$-O`qRg{twHRa`LYHDJSm2ua@Eh&V#B zb39Vvtt$n=CD7^@x|@g@q-aNTb_$p_W?Cek1+m6^`UqFo!waq9p7Q z8z(E`U_IiM6cQR&7LxHQpz1G4PdTnC^tZ_vzpL>ZZLAK|T#9E{bBUsczOe4{|714i z*6_)XwdB|I$=f#pK4Cg*5j8T0@Zy5CE!A~`R0q}d&5DB*TPy%l947~)xULZWeL;w< z#tV_v1W>Y)(MeWmxeu!AC*q34)&n?!z%!JX$Z4qSwcyf9;0-MBhCZ+@!?Z@+BzoW* zS+I?RU|mF34?OA-OiF#y9_>)w;uOmmVUkUDqtGT~2sv@tkGj2I6e$he-c50<>-KJ@ z=s~x)Rqu#)b0H$%LWsyG2@&y@VASp1iXSX_$jf(ox8YTl$mL-rvaS5uvY(nHwj)2( zwLMUCJMav3E6rG6Q?;_ra_?wCc8Y`4&sXhi9tdC;UL=5Bg$QOhAvU+Wa2=bknyl9) zY`*H6knt(F`T43nc;g^}{Z41s!Y1|(J`~mFSJ?KAKmWE(G+piXQlb;PMP_matIg7Z zuD#^}UHb^3D`!<^c>qvz2l7Vsq_B{)o&X8ZW+LO zhx}nZCxrEBLRjwc)7brOjRc#o2ADB!)g)fMnQMGp#iN$&`)Ux+Zv zLWJpr2(JQ01-!}+=C$SJ3-}?tO0+kGqMa$fwttViS)_qfX9G2ND9^CaN`kSO9cKPt z_WpKGbq_Y%v74Eml*Wl#pyJ4lY_6i^r}*oQc2R=dCF)jJBZ)d``mWO9mNFvwiu@7D z5kf?Dq!9c1st^$!1xg6Fg+e%59&P^)Nym@^BKR6mb6@8v!wTpW-*tv{tc5zxhsy5w z(T3$m!E?8-XnF-R`vytY2_`bL_8nd&;X6VJe^-8O^OM)pND+yD52(5C^9+qn(v6Mobn`#M`)4;o z*84QnX=Zmerk{r4adT&|+8OoWORm+ONdk3}^(J?grHgpZmOtV-M~K+Y6=HJd2@%&1 zKndi&P#`~)N87(n(2s}@`{x7fnC2;i3#b&&bq05Vg}Ts(%8nnx;F3E!CNTpTPCQ!e zbrBKkn2*frVkOyNR+r#bXI7UgdSF(U=^gp}M2OseDnxFV3z5^$z{sqw;0Ke|^73Z& zb6zFI2SO?SLVj(ZoTICVhxD!nYVI1Iq47vkvGH7M{?~c`Y%6jL=2Gm4u~v4%_FjhL zzvgPEF#Pvo$EUgnSoEe&D^^?G^(0d#MQ=hkSn7!4m-0sxHwqEaO+rlQW+9@v1r+m- zhRok8kG6l8tlLNhQQQu2B{NSMQb4QtuQQ}OE!3}ksO*Fx3@J99^ep!E#O+{u78TT8 z#H^D+WJz}`%?3-l2e&#)x>wNyOS(_*i0OVI;(I`d_#PA@u7|+Lk{;#;0? z`dKsd*ZwnQ(379V0%CQ5uSJ&n#xtLAux}fKjrM*XAx(AF=RW|O=9|Bjl4}{4&dF^RUtG zcWa}AJz2qCj6Sw^kepIVyQg8ZPR-ubpE2&y)wA+PSI-I2;qyXF{RJVq`wb}VJQr%` zMI1Dju?rSA-{j_gORA{uC4f7pEsOMg&f;ZKMH13}JVp9@%kY&TLk;R}DrlZn=w4N- z46I+XBoOH9azLPe5F*e&3K8g^ga~vFsDZWU(7^f)p|3Zs_d1WpK+YM$_LsP1#Q?^S8+_(r-9n_;(}aoq!aF9QM=NzrXN1 zIcHWsQD_}s`X9j&6x%hGOO53tA$;4|7p<{Y`dM?J(5ic^@m9E+!P%G`TM-;zFYKzc zx_@}eb`6gCKZ*Qml|-jfF=^G_ir^S%(1`9O%td< z6=X?IIj$y5%qFhRZM!^#Jr)(4(BYNtqO#p6d=NZ;lDz9%=>5i zskqqCteHo~@7oPwVhdBb}F78Egzo`&vPc* z-AdZEk~tMiY`(rvySdSX4Q?TSY;ckg+uTw}&ApWno7@_d)x8y3-8ML=J=Mvq!Q8lQ z@kTP+0X4V1h4s57hX}g^-q0o~#l6fOE#^)^%rWFrFCn+HV$m$^VnJYGS2@7KZbDet zT?h-4g|ILMRO6$3)cEWn^b3qh*}FY)hxEOGnrq`3Dpe#k^b+^9Mo1@s=cBIn_5|!> z1Y|q`+5bpR`;vGhd77JWbz(ouI_t9@d?CjE=7Go$P<%vwpb(KCBt+y>g)O!^@g==3 zWvdhKgtpU;n_r#i;H~Cz_S=8xu#F3TWW&+LKJ0tZy=SVMD{h)no7gS-lFJgEmH-5G z$peD&LI|2JgrFHhY@h(jj3ZKt^6+0~{OFv!35Zo34Afi?Pj!s~a>PYk+IuZXpAV8f z3k%sM+{_bCxKR4*_GwnQnyEcMeL7=cKgQLRvVDk0on;**=xd5Z;xC%ek%I8llByDa za3Kz4dy1G8v@4U?z7vL!B%4T76-T}+?MhHuFH`Gk_ZtJqv@CyQ>V(L&B1EQDAu=5R zCDV69^&WzQs)ps}%DS6L5TrQ^;4XLzpZ=)uhY|!DCEB0^ewao5au9VzvYM+rT`i8C z?2R9;$Z>D{EBv(HxU}SsP#o)x>nrb*ky$#SBX=a;$)5RF6*=gcAEl@W=x8AVJ4Rva zdgfol#d_vnR}f@-tPoiqC#>t4zpJY18**09{CHk!?gXAPKjVuk@SFAA15!d2uZo^<~cyX$^hZb8)Nd%bus`L0|RO@|0{-RZ zfk1x7iv)6o5J6ojq~QHrxVklwztHPwHIb7?Ef7lLDjaunJw-Rm zheM(BNWd)wN0he$+|YN}66`)A|e>V?xVvh$Tn$}fA!X-3Jq(UzlcCTWWgL9{(h zHs{TkBM`RgHJkX(=Q<0%+(BA23(f5+_Q~aH$~GUQQ_&??wCfF2Spy$2ky2i>qZBnv zP@^3r?Z*vlG!7DOvaf3>P$+~}cPGNE*S@vszcLx4{k!Ck_U{&={d5zYmo5 zn<9I>AD5PJ=hOrIsJRE_#O6EQbzLW9_Ygknn?GzJ$nV#3Aiqb1^mf>?g!|Z%X`FRox z7>)-$FIYam3Gx{%*B2EDx&GEdAlH}VfLwnkM6NFjk?Ze;$n_O4Dc4u!K3J}=;f!2g z2WsvQJVUvPpa!`fC%JxBN|F1cr~FSw`5aGq_OnIe8&EY!({CEVe+~#9tm(HD2~GdS zLZIot$^lLPO^Bx77NY6D3(@pDU{cfnko#awzl$@Peh;X*fAS19ErJ>}?T4HidPFtL zzh5)G?+N(82>8$w5DaeTb56s!t|yXvq7$|N?(7`qsmiMjb53@`q7V2anxIJsZZtl% z@%YpxQ|mfl8%5KS^u{(${@BDV8eVurhAwVhzWwu8TE7(St75NWG)+Q9>+X zv=B>Z5~BGnC8wD~*gguD)&=DH5a@lDt++oP^loO>W`)wpXmC$x)dk7v`Qy6Eb%x-EW9 zUHSfeUzgf%mXv1N)J)iZMuT3PQW(!wZi!L1wH5R*;;u!yX&+*SH@y*uIXD6Y9Riz` zP^)XZy>f2GKrh{IozAg&JvG>B^0^9t##~NKQA(a*JLMA*s8quSzSS*@V6ffTeEf9X zawacqV0rmt11kuzjTME|1}h1%iIqWVedS2&tKgzRc?Em_FNoT$O8(fzY5*7H8!_qs z8ZjyIN4zq^xba=XNE;iFHn>V&Q<12W*Rl{OVQo2}gmr`{p;?F$T7)QJ9H=T;*{D;q zu5fUb`~{qm%y^*YCh!bPj0kF|l21_M8(f9)nK0C}Cw`(4zn&*P`^%j-;f*En&Mk*E%q(B*3oSgOJlb22;s-Q`U_e4XZPr;3AT0d@I{CDj(a2t89;z}ZFV>T8?4QTsXl(zt*Hf9n(SRs&?Z)3LNRkckjs%^HGXIu7vO`_XC zHbrq;pysyY85TtuiZx#0@PB&?vO^rCe)zwmc_7H0c#$A?79!wXgwzqc3Y%^Cznfl{ zwBdg`65#GQ`r-d%-fC`&{Z8ABhTG}ld^K=6n=|rXSEQ4+!z|8f$vyZY+a5}IVz)?5 zP66y`NkQ9Q@_@EBA++r+gtmQzC_Mv8tua;m%BSrU5wRap5UDJWkJ$1=q}>5!VlD0x z9AH5X^g*)M&?VTTk58Vt67w0oei5Lgrc{G_Hkqg-iCG%;@WUtU#%8;j?MU;VM6;Q5 zHNEyZEw#_K3r*3Z@jh#3IfAH(Muz5$d^y|79o2mCV4%OBZ~NF--`@n=?hO?s>wez<{q zW$l`=IE(3gv5xtkCe1^*{PbLBm8{ewnO<~k+%!7v_SaB89Nz6I_051+eL$=(Id0q_ zW}nguMQ8mT7SS=O0sEkYeWQ-vj20S712YOa~FI%`WqJG_#nn$ z3pzoOVDg(50wzzC15AEP2$LrXVe;ETm^>LwGI@&J2V2moIK%6AfSUU*&yZIU)L=o| zdJFnDD_eJ(&+>bg<@bG-+4+?A=_EHuwr5yQX9hV9mhD-Jglx~Y5Xkl%IUw6}g~;|i zA+r5}5ZV3^Ov?60avv<)^KnMDKL%><0-m94MNormC(DlJYs+(?&;26H{bHYccK&B` z2`NVsSD#RK%3o@kUKV6}uGTRo(@EiWkRCqT={L$V(I2(^(TCkMo0_U{Z%A=E(;Hgx zv+c2J%GJvm-!x!bBJ{gM!8%e0=V$X>C+-{xrXBdaZ1k-qU;S266XWw8;R!UmMxNH2 z&V~P0R9;<1O_W%>Ur$2N6|rtWv9~whrEhpJ^%8u7iO)Ml$4Bzcm0Yye%hn@08P*e- zOTViwBfnF>Ngsv^x^n(<0*LfoEu(<1h&X=(qjmZc^k^bRUmQ_4h%TDEISk-;M%Z^p zj1r%e9gb3e&T>n;pLjhcy2#d1`KjrUGI6>5DHA^vQYNktQYNkxQYL;5s-yD7sH5@= zT%vW7tN2lKSIdd1M%CWlBHT4NsqkHEUSxEg9LVT;AsO8uB%@yn$>>HTUsQ?pB^*sS^nerEYhXy7_36?lzzK?Uwl+KJ)B+&*e@M4kQrwL4IX< z-WB9&61EcJ?pA0de2;k{;d|wPgzpm~;roS1_yHjjeh^Gb_#wG_2`feSFu#%TuYsC- zgl8yWka$UZh6&kRX0zCzNzLo z{bu*HCqJ&lqj|7rM0&$K*t580`5V?%;+`&Uxp_{}gL$y$^-ksRf{;q%H$p0n7ll+3 zzXhXtu$TD3(yP4uJlM;;sx{a$Y7Krb&$jGmR&=jGAC=mx0OzrIhLxIBPo)<0Y+g72 zKX`u|z3$>KN3-n@^j)gX&nqz5(W|+Px(42NIQpHY4y=i{gUXtzY9eUPO)GNcDEl2E z_1YqeZaWJXQ)Yz6Q|*e-UNz|cb78@;Bz99^i0$mF=&&u3e2!97_|8(={Sm^k@7O^6 zYRaFCAGAD2{%H9PA=-aaNYn6VAzFV6lwNm?^!gWEG#&9R?C3Kl3inrNL#KZOYVK_# zH2s+*^zYDyT4Y>tgZ_?@{f~fb4K!ojx_1?WM*ThWMsxp^1DbnZh~_>JqPY)+Xl@wa zN>`(ryh8&871`g@D*q0xcv%t&yz|of{C~U3W(5nmq7P}C#m5KJ&6|jemCOS! zR^~-qtRjSqRfTY|ns5!93SV8Xi`Z27kSgb>jzgoqX)q8taRsK?o_E1$NZbNd2*2zETc z(j89(+Z~V~Vyy=4o{dZy9;PkSL?0@9Bh>GwjqTwVz=~CFU#v2p8w`RpoUzYa1dX7- zhMpmyt|_ndv-dODxkhC;+rE0J4;5*AYC2UO zD6+R~#sKSC9HFM>Y-KXJwZm_V(|FZnyoLRqP%TZc-&^uUu=QZ2PS)KuIF-)VHzpDH z1`2|>Hxwf7jf9AMVVz>RsQ6W%-E zLiQ4CT-WYsfp-c5Q`-#-jM43^;3#qz^Mkxy>UKLf#ZmRgf|efqMv( zU$@#52k6@i;8YFIunrOt4Rz4*P{Nj@&q{f4dwbgVG1@bp_Uvbk$bF$J(y)DLX+NVk z8_>HBLY=pg?e>q83T*XJ8N&feDLyZ8AV2N1Zd4cUAVqod*uqm;9o4F@E8J8(lZPd~ z6vnJnyIi|sVo4oBEGwtLb*Ck!;c~2c`xmP^aFm#m>q@h9t#I0n9eg-f?`;3_``Y=q3sa3VdP>uBJ+_7! zWmf9b7hHO636o6Nkj00DORN;z^#L~5XBh`;p%%~Y-Rh@p)pTc32PjJ<;(|#>Bf)N2 zF3>hgR?KzI=+iu5u>8yk%2~)6rLQ1J+(u%>PVpGzRFY8L7+_)p}0UDWkHViL9#bepfvp+PS~qo z=JM?SEA=suE$XaISJd$gChUuq*04jz_CmA0w4X<}JHnd+r>>$M^Ygzr?3+7S#-9ql zvq4{tvNqoD39vH^y^7d*aBfDP1Mv2-G@EaUFQX@oQpyjyXPYFWv9wK#t6YTzh-vz2 zlO3lh+2JDV9)=XyNd*#M?p^&)f2|LF&N#Ui%9Ste9IepqMQv{!r|c<-b>HSrr<#)j zXb#zompv%?ltSy!!mNFmDbC39FAel``OTk}lx|P&?-^h@BSAVT1Z0PG9Mz2$1=8*q zR5?VM{hDc*GW&J;Q)Z79Qf7}6Qf9v)q|6=~h~mDv+;QO9c|TDgD4INUc$0BbxE zsJU-hYUzJHwUbBy24%Q$x9;1P_Q^ro$!^^#3Qi+A7P7@-4?+FpW z_dzvv%0Nxs>B3~U?hG7Y=S-mH&f*!ia3Z3iTc_iZ<5TWzALSg2a;}eJM?)Ln9bsk?o-iRnkY*yzXJ2=&vo&M|`4okL^6gev}A< zWb&A$^>~n0%4_>0>v#8r(m_~HS|o(^lpGM&(?W#xj1XZxD@0h&fk|OKFZZ_WYGn4& zZHaqZ7e7Nc75IvnZ63*X2Pe`Gb&B@<$=13It;70bU^5HkF$9LVr*LNa_?NQQqGlHog` ziltani~0}Y5KYg!xKjw<130L~Gc1H6si6>32JM>o_bu=TKCq1&bxpi-vI*PpX6O$s z*s#%_sndzAWxbLs&M4}mE|XHxf?nLgwB6@hCmY9>DAf2=bJT5XX0S%jm@D5BpGq2M zU(D5}d5z4HUjYujs~RO0XQ&&P2B#G5qxT6JRUe&3$W~K=gBCibb+zrkO1_=EtxtPiX(2f&q;| zOH?F_12wk<&#)ppE2zRsQjJ7(9>-X2T} zwYPUL64ctPc5wNsy2Ey2g$uID3Z$deElqZH#_bnomoX}^&}HS1g)S$=a+eoUnXVwj zVpjxZlQScmTnQJA&sshK18!v^A)-})np@RUNPn^vRwELONQm(`Y;{X76{Kf7Fq5Kk zYbX+J$5;yiyKBk;cGnWZ?%G1wT}KGJ&7j&2WuvyEMd){6CK7hzaE6_A0X{g-Gt{^U zYG^tZK~4#sx$!>k1dE&YaqZ5`WT1(-58`$`i?V(YCE3s4K*8a2L-T{rjpP8I8w=rc z6Cr$lQ3#)#f=NC%lY6qC---hqZVuGk7Cb`^MMMLK8cQW@&`t7@x3tJx`N+0pTNi3; zJO=T&jm6nEh%-cYU^~Tv)$J_^tnMHOSlv+wt2+r{b!Q>0?gA!R-Bs>GbO(0B9gcSg zYHl*mkYkb5z;W0em|}tV@PVW5fVWY4SYdZyPYbqJ5Nu@79k9ubD8C7d`a=VQ|8katV7Qquw_0h=A)Vb=XM z;ijXsZ$a(*viYudf0tYQ^nDo%Zj!%uH4l6mX&0$|D*)pRnCW1q_`L4094VhpA zL$tknOIi(W?>@NIwY?cd58B>+^^X4c6Ji%xA$GC95IZ;kjN0A<`N3ss^73u(R9;oj z9U9ehUy^5A_H%7{vy`^l5eY@41K>(Io?#J@fyWKqH1qHD{@Ir93-lJ7>!bK zPVuM+D;5aet8#$%0U^8}B82yuLU^AAsv=ZQYTITD{i0gE#NDCzz}aB{$6t7cdKOs? zRiT=zk2^FP)5 zXSYHG+obx6Z2h5qWzmd_S!`*Sqn`t|juR|M`ST6oCur3r@2%u}4#{(|`gcfch&t-K zmIc~6P5x->dqTAMeIe!abRpV11C)MFjP!G+eA?ym7ot zwC8Qg#Qgx-*4aul_wYkWy`fV05pH#r!ug6GR0==VJLYtO5Hq__h?!j^#H=m`qe|ft zeoS?j%F9;@m+>m2J1H`{pUAWAbFIs+TjsCUe~M@+gqH&~_cNYh)uaL!S4~%#|CQc9 zdka-lFkujW3&w^3{@lL5sK9@NI{cCZT?XdMJA#A#eeCwIO&r02wkEAJ`ykN*x(zI< zGdTwkUbZvNU-xHYmFRjN-HyOENbO(I8nTw;baEXh+xVdsXBCS0VxNgYMcL|p4mWiM z?>lh6Feb6utK^T>UM<9$uMtu?UMs|kuLEVLXGC^-Jud3Nr9ZKJ+zmuU55ELz?nX-| z{ja2R6H#GG`ii@7H(RQ=1gR!R>$fU6J-6G;52kOI15Do`gy}nlF#RhbOy32n=cWwQ zbGuuZ9IfAj1B~1Y)ZBeMLxU0#4IQ>skkdz*guCA-_JAe!pieCOv9TT^;2;@1Y-#*D zNMnf6{3D8mC?2&Sh~hChAd1I@h~fz$qIgn>D4qh7qIg>FLyYF1!5xV_3)I|mJVS|y zqy~v-D$YyfKWh5Vdy-x-l78b!vT49iA@3K-F49h-!~I($=B0p`Mdo7*&uP+Xu`@WK z=0$ldR})Q0G?zbuJ0 zw9dcBt*&)`MbU%S`BlAR&#wuw^Vfyg`5%PX^&i2gb^a4SSU8iHZ=K)ZRkhwZQLXo; zJeipMTw`%xtYw`0Gh(6GzXh;j$TKYVGW57*{j2%^&HHD!r2OwsJEnFM3u8YT-&gA$ zU=t>H&uARbSxD|f3y+F3FbKv7t?q4-qI|`c;+wUf~e+V&ycZF1I?+H== zKS3$@qDaB-%cpIA(Dnf-A(Ib*nj6M>A7s)U5G+BMSi&y<0{C-vqYss>{EOx__p$o= ziK<|06Ybo?ZjcB&mw~`|yPwI%DSk2FOf=J z1v9USY*3d77mI~6tn*&+EQI{)%qbccEG*45R6N6RtE+fMD0)!wjMO_8H%f>ljuv8x zO+qYh5iqKF7Uc(rq2%Q&o-w@2E-#7fa&dWb$>Hag5jQ=bqGc?B_$kaw0$l#dGpyiL zaN`PYY4cyk`)5}~S<%(l`ViybN=i$AC5|`+drENB>L862ZRrot8$5Gc%sbqqzBP#xIr20&FA89N!BnL#|AXAa6T-w8n>pxGe}s=JkiM7azG>N2+>Hh5RJ45 z(a1PZg9PQE@zT1&HoG0fTcY~{E(m5kz_CJ}VKEXJes~aF#Z=Vr;M1~Pm-duRG|JZV zlx06%64oczSURtn@(lGSGQIw)RC;#iXBp{lsDi(hPh@LLWE-DIcCMkf zB|s#VaQobL7I*s~?%CwDkA3Ta?CbzQt8OH2?XZqc+Rh|c{S;R zUtfi8>Kf1)1)W*wWZ~8h@$mgIj^N7G>f|^AwRDkl|IsX_#z}T~)qk-_LTuv7W+tb1 z@LV)@RChEEP}NTIM^!ruQPnO&RJE%RRqY0~_aRm7PZS^~}4;c%h{!K+Wx8 z;rtfAhH!i0g=i&AH1-gOQ}?ncZ9x=$0d24(-QIDuv0mJM{7#;r)9q^E=Z5xIB!qB)5TP8XuyqIM4#LGgH#Aj2kl&Yt$g5qL{M?Wv zN0rnecYSXt$4kvk0sPZQV&pby*$IBB+9HP#a{$H9GU%>4Y<#fTQYL{J-OM> z09|EBiEKDA!{Ut4NomvB@mGQ=VoEG4?}+82e#DO!&)SG&ZU6gEM~e@?(>)@T#il zrl=}9LY{4Qmi>QR$Vb8nh4QOF%^k%vER-^*xSM~p`5)u`?GirU)N~AWG%lR%_sx(d zJ6O=P)18{R=l&nvM5}M!^yd0F2j@7E`886;sAFCGuJP9`Llkzb{889(LKOK8A$8F4 zLKJucC>7lmspy-yM0>waAZSy0iljT58rwGaER3SNiM@UZJ z1y$QgBC7396DFs~zlQ@7{XS50r}GRYDk2(s3_6IzQK>V0lrt^LSw2d5jOuKD2HC|^ z{^wZ8bAymWOn0BBSg`d23j$j|lml%2NC;c!3t{WWLfE@2K-=^;_!X54| z25RmSo*{Q4se!xj7*)z$>SJAIv3}xXMaQU8?x(m6;_-3|{IekNpkq{5C=MK6X#wEy z=W>9wo)wFmCk1Lt* zkUt8# zQ;33oB}74Y2~p79pvv$)Q5n7mm$1iI&z!p#FZ6UDP;>WNIN#%I2=@S9h)%+ai_e1= z>!Bc4vO0KJ!Lh1en;%3!A_s_mR0z?J2_gD%Aw)j`%Bqxss)Hwm$?D)K93b>*pyr<8 z85)#`XfP-neSK>Bde&3+oKg0?r_2ukKQ$6wAlFDciN4HljHVX@np*0n0QYnpo3;^* z&8D{aiYd|e&ICZEk9GaDhdLD_6j!&kgA$7K{`lOo2so~PRPq6gU8co?I92-Bn zK81DgD4bnIKPlCaLt=4B0n!2G`clOB)Q0h#ZZa3&VXE6a8-{nk^&*W+Vbp7SN#bni zHT@2^x?a=EiXQZuey?|I{uLo*;Z-4J;WZ()|2i1;n*P8KR+r@Edrg1hRmJ)Ks5sA& zC;Rz7SLc>VOB!ksF;eFn$c*aaO@PB|Jj3ckMTF`jIP&q9`TxcHXCI?6*oKJ#b-c8- zj0I9AEwgF_*>^!1&O8Pn}u-LB80i@LCIB^;<{9!QavJzMBkX#9M$73$&&zto%le*|>}Q?m4WKi!g|M5wp|P-0z`~Bm zeMfeqX_qK!7347xT|opjH*F@Mc4EJ#}79l^zTUZ>+%=3vCplsD%-?z!l=F| ze~fBVAx5>C5Tj}pVpN-hs>)u7sh_8n&#msj&**}(9TmkcDARd`M)|s(@J>#!?i|L| z6=l09E>hf8h*Wn|;JO*s-EpxQ*2xNj0;UKN_#VRGiZX;w1-vIN;j+?RymK2KPl|1~ zaYYrexBW(&`|zT~GUhO+TXa}!5MEKXFAg@nx}QQqq0TZW+x%@O zYI_f7++7LhDVe1rUg+mnDwNcU>$hlSo-Lgm#vZ5^dn)NZzA05{s?4rb^SxovCg%vm zTzduJdp3$838}Fdupd~$A-rUU_t5Ld3UHBH=&kX(+wjc7)!AHzcDT9kGG)g-+*f!Vkf9vGi(S2NBrjZ-#WSMjDEk35 zM=KB+93v$EuL+^&>q0U-7L@ggBPDvAur0eWmb0@mhLnT3JjH1aVQg@>^91X;agIy< zL5v`tTIfob=#GSYxM+6V@A2s6Qei zLOma-xgYZkg(~WjLhbfK%&QpcZUYMJ1q7Lr8CC3RH?_G7`Mui$nZ=T&uHr7jm970e zGmC`<$``duaI3jX<+B7d7Ig@--CIP(U1rgKBF|C5T4crjl%I)`#O4d8gW1}>Z?0#6 zGUYDEzvO<#v*xZ)*vwK<@t?-7IoVvrU5O8LE=_){nYa6I#WH_Yq{RLlzbTnT-Sp1( zX`B>kA8x@itrI6!OC`#1F<+gYF1Z;K3)SA9iS88@fAZ}&g_aareXBUok$F;F2QSLF5g zAPjfAoQHSirwz<-cPORI68YX~oZGEbXLIjkA)l|hJ8?fQvnpS2=^v=hE&X39p%EgD z8>{ZpkHspOOW#0m`z*B|?rz*r>f&7kz5VS`Ra|lR;7+ST?OW`dUJ3%=>jRJB7UVwh zLb~}}ue;B}E-SU!fo7E7P9+U1nEU;BW=7#RVeR!c>T(b0=aPInhm$t65(AY$iVrI3 z;h7PvR7@|Qo#mHeH4tuv9L*w4}L ziJB;zh@n&_HZ(`+>a41JS%DXq4+~H#zU6T3g^eU*C2HWbf45v1FS%^20r?Xf85DDr~EF879B#=w5kB;LgFxXjAFO2`MF zxwJ#~?VUjd@n?cizye)a!Kt{naGdJ?q9m8IqQteYd8f@I6mY4w%l#Ek3hW{-H#2hk zn|E8xq!-otZ{s>;U|MF`#txfrcK)tN!!ipMyDIJ--fQk3JX;ED_{pBNx{p|x#&MOx zeuZZL^@mhpd|}P*i0#`F&9PW;pIrx|}=jx^gJ2x-PY1fvebFs285 p2Vw!eZ8Oy^2o7XMhfP++ErbKJJ3KQZWchG7oF6qeLLsN6{~u%iN9_Or literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/.doctrees/index.doctree b/python/mock-1.0.0/html/.doctrees/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4c53f11a5aa77a94f13d9ac3e70ce026b351134c GIT binary patch literal 98784 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 literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/.doctrees/patch.doctree b/python/mock-1.0.0/html/.doctrees/patch.doctree new file mode 100644 index 0000000000000000000000000000000000000000..100ef505db9b77e3f7e131fbbc2857fcd30f6373 GIT binary patch literal 123511 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�uSs8}yO`iA+yNIoo(6o12f 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 literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/.doctrees/sentinel.doctree b/python/mock-1.0.0/html/.doctrees/sentinel.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e0312ed60768bcd144ea8ef911e4b96358644838 GIT binary patch literal 10632 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 +in Python 3.3. + +Full list of changes since 0.8: + +* `mocksignature`, along with the `mocksignature` argument to `patch`, removed +* Support for deleting attributes (accessing deleted attributes will raise an + `AttributeError`) +* Added the `mock_open` helper function for mocking the builtin `open` +* `__class__` is assignable, so a mock can pass an `isinstance` check without + requiring a spec +* Addition of `PropertyMock`, for mocking properties +* `MagicMocks` made unorderable by default (in Python 3). The comparison + methods (other than equality and inequality) now return `NotImplemented` +* Propagate traceback info to support subclassing of `_patch` by other + libraries +* `create_autospec` works with attributes present in results of `dir` that + can't be fetched from the object's class. Contributed by Konstantine Rybnikov +* Any exceptions in an iterable `side_effect` will be raised instead of + returned +* In Python 3, `create_autospec` now supports keyword only arguments +* Added `patch.stopall` method to stop all active patches created by `start` +* BUGFIX: calling `MagicMock.reset_mock` wouldn't reset magic method mocks +* BUGFIX: calling `reset_mock` on a `MagicMock` created with autospec could + raise an exception +* BUGFIX: passing multiple spec arguments to patchers (`spec` , `spec_set` and + `autospec`) had unpredictable results, now it is an error +* BUGFIX: using `spec=True` *and* `create=True` as arguments to patchers could + result in using `DEFAULT` as the spec. Now it is an error instead +* BUGFIX: using `spec` or `autospec` arguments to patchers, along with + `spec_set=True` did not work correctly +* BUGFIX: using an object that evaluates to False as a spec could be ignored +* BUGFIX: a list as the `spec` argument to a patcher would always result in a + non-callable mock. Now if `__call__` is in the spec the mock is callable + + +2012/07/13 Version 1.0.0 beta 1 +-------------------------------- + +* Added `patch.stopall` method to stop all active patches created by `start` +* BUGFIX: calling `MagicMock.reset_mock` wouldn't reset magic method mocks +* BUGFIX: calling `reset_mock` on a `MagicMock` created with autospec could + raise an exception + + +2012/05/04 Version 1.0.0 alpha 2 +-------------------------------- + +* `PropertyMock` attributes are now standard `MagicMocks` +* `create_autospec` works with attributes present in results of `dir` that + can't be fetched from the object's class. Contributed by Konstantine Rybnikov +* Any exceptions in an iterable `side_effect` will be raised instead of + returned +* In Python 3, `create_autospec` now supports keyword only arguments + + +2012/03/25 Version 1.0.0 alpha 1 +-------------------------------- + +The standard library version! + +* `mocksignature`, along with the `mocksignature` argument to `patch`, removed +* Support for deleting attributes (accessing deleted attributes will raise an + `AttributeError`) +* Added the `mock_open` helper function for mocking the builtin `open` +* `__class__` is assignable, so a mock can pass an `isinstance` check without + requiring a spec +* Addition of `PropertyMock`, for mocking properties +* `MagicMocks` made unorderable by default (in Python 3). The comparison + methods (other than equality and inequality) now return `NotImplemented` +* Propagate traceback info to support subclassing of `_patch` by other + libraries +* BUGFIX: passing multiple spec arguments to patchers (`spec` , `spec_set` and + `autospec`) had unpredictable results, now it is an error +* BUGFIX: using `spec=True` *and* `create=True` as arguments to patchers could + result in using `DEFAULT` as the spec. Now it is an error instead +* BUGFIX: using `spec` or `autospec` arguments to patchers, along with + `spec_set=True` did not work correctly +* BUGFIX: using an object that evaluates to False as a spec could be ignored +* BUGFIX: a list as the `spec` argument to a patcher would always result in a + non-callable mock. Now if `__call__` is in the spec the mock is callable + + +2012/02/13 Version 0.8.0 +------------------------ + +The only changes since 0.8rc2 are: + +* Improved repr of :data:`sentinel` objects +* :data:`ANY` can be used for comparisons against :data:`call` objects +* The return value of `MagicMock.__iter__` method can be set to + any iterable and isn't required to be an iterator + +Full List of changes since 0.7: + +mock 0.8.0 is the last version that will support Python 2.4. + +* Addition of :attr:`~Mock.mock_calls` list for *all* calls (including magic + methods and chained calls) +* :func:`patch` and :func:`patch.object` now create a :class:`MagicMock` + instead of a :class:`Mock` by default +* The patchers (`patch`, `patch.object` and `patch.dict`), plus `Mock` and + `MagicMock`, take arbitrary keyword arguments for configuration +* New mock method :meth:`~Mock.configure_mock` for setting attributes and + return values / side effects on the mock and its attributes +* New mock assert methods :meth:`~Mock.assert_any_call` and + :meth:`~Mock.assert_has_calls` +* Implemented :ref:`auto-speccing` (recursive, lazy speccing of mocks with + mocked signatures for functions/methods), as the `autospec` argument to + `patch` +* Added the :func:`create_autospec` function for manually creating + 'auto-specced' mocks +* :func:`patch.multiple` for doing multiple patches in a single call, using + keyword arguments +* Setting :attr:`~Mock.side_effect` to an iterable will cause calls to the mock + to return the next value from the iterable +* New `new_callable` argument to `patch` and `patch.object` allowing you to + pass in a class or callable object (instead of `MagicMock`) that will be + called to replace the object being patched +* Addition of :class:`NonCallableMock` and :class:`NonCallableMagicMock`, mocks + without a `__call__` method +* Addition of :meth:`~Mock.mock_add_spec` method for adding (or changing) a + spec on an existing mock +* Protocol methods on :class:`MagicMock` are magic mocks, and are created + lazily on first lookup. This means the result of calling a protocol method is + a `MagicMock` instead of a `Mock` as it was previously +* Addition of :meth:`~Mock.attach_mock` method +* Added :data:`ANY` for ignoring arguments in :meth:`~Mock.assert_called_with` + calls +* Addition of :data:`call` helper object +* Improved repr for mocks +* Improved repr for :attr:`Mock.call_args` and entries in + :attr:`Mock.call_args_list`, :attr:`Mock.method_calls` and + :attr:`Mock.mock_calls` +* Improved repr for :data:`sentinel` objects +* `patch` lookup is done at use time not at decoration time +* In Python 2.6 or more recent, `dir` on a mock will report all the dynamically + created attributes (or the full list of attributes if there is a spec) as + well as all the mock methods and attributes. +* Module level :data:`FILTER_DIR` added to control whether `dir(mock)` filters + private attributes. `True` by default. +* `patch.TEST_PREFIX` for controlling how patchers recognise test methods when + used to decorate a class +* Support for using Java exceptions as a :attr:`~Mock.side_effect` on Jython +* `Mock` call lists (`call_args_list`, `method_calls` & `mock_calls`) are now + custom list objects that allow membership tests for "sub lists" and have + a nicer representation if you `str` or `print` them +* Mocks attached as attributes or return values to other mocks have calls + recorded in `method_calls` and `mock_calls` of the parent (unless a name is + already set on the child) +* Improved failure messages for `assert_called_with` and + `assert_called_once_with` +* The return value of the :class:`MagicMock` `__iter__` method can be set to + any iterable and isn't required to be an iterator +* Added the Mock API (`assert_called_with` etc) to functions created by + :func:`mocksignature` +* Tuples as well as lists can be used to specify allowed methods for `spec` & + `spec_set` arguments +* Calling `stop` on an unstarted patcher fails with a more meaningful error + message +* Renamed the internal classes `Sentinel` and `SentinelObject` to prevent abuse +* BUGFIX: an error creating a patch, with nested patch decorators, won't leave + patches in place +* BUGFIX: `__truediv__` and `__rtruediv__` not available as magic methods on + mocks in Python 3 +* BUGFIX: `assert_called_with` / `assert_called_once_with` can be used with + `self` as a keyword argument +* BUGFIX: when patching a class with an explicit spec / spec_set (not a + boolean) it applies "spec inheritance" to the return value of the created + mock (the "instance") +* BUGFIX: remove the `__unittest` marker causing traceback truncation +* Removal of deprecated `patch_object` +* Private attributes `_name`, `_methods`, '_children', `_wraps` and `_parent` + (etc) renamed to reduce likelihood of clash with user attributes. +* Added license file to the distribution + + +2012/01/10 Version 0.8.0 release candidate 2 +-------------------------------------------- + +* Removed the `configure` keyword argument to `create_autospec` and allow + arbitrary keyword arguments (for the `Mock` constructor) instead +* Fixed `ANY` equality with some types in `assert_called_with` calls +* Switched to a standard Sphinx theme (compatible with + `readthedocs.org `_) + + +2011/12/29 Version 0.8.0 release candidate 1 +-------------------------------------------- + +* `create_autospec` on the return value of a mocked class will use `__call__` + for the signature rather than `__init__` +* Performance improvement instantiating `Mock` and `MagicMock` +* Mocks used as magic methods have the same type as their parent instead of + being hardcoded to `MagicMock` + +Special thanks to Julian Berman for his help with diagnosing and improving +performance in this release. + + +2011/10/09 Version 0.8.0 beta 4 +------------------------------- + +* `patch` lookup is done at use time not at decoration time +* When attaching a Mock to another Mock as a magic method, calls are recorded + in mock_calls +* Addition of `attach_mock` method +* Renamed the internal classes `Sentinel` and `SentinelObject` to prevent abuse +* BUGFIX: various issues around circular references with mocks (setting a mock + return value to be itself etc) + + +2011/08/15 Version 0.8.0 beta 3 +------------------------------- + +* Mocks attached as attributes or return values to other mocks have calls + recorded in `method_calls` and `mock_calls` of the parent (unless a name is + already set on the child) +* Addition of `mock_add_spec` method for adding (or changing) a spec on an + existing mock +* Improved repr for `Mock.call_args` and entries in `Mock.call_args_list`, + `Mock.method_calls` and `Mock.mock_calls` +* Improved repr for mocks +* BUGFIX: minor fixes in the way `mock_calls` is worked out, + especially for "intermediate" mocks in a call chain + + +2011/08/05 Version 0.8.0 beta 2 +------------------------------- + +* Setting `side_effect` to an iterable will cause calls to the mock to return + the next value from the iterable +* Added `assert_any_call` method +* Moved `assert_has_calls` from call lists onto mocks +* BUGFIX: `call_args` and all members of `call_args_list` are two tuples of + `(args, kwargs)` again instead of three tuples of `(name, args, kwargs)` + + +2011/07/25 Version 0.8.0 beta 1 +------------------------------- + +* `patch.TEST_PREFIX` for controlling how patchers recognise test methods when + used to decorate a class +* `Mock` call lists (`call_args_list`, `method_calls` & `mock_calls`) are now + custom list objects that allow membership tests for "sub lists" and have + an `assert_has_calls` method for unordered call checks +* `callargs` changed to *always* be a three-tuple of `(name, args, kwargs)` +* Addition of `mock_calls` list for *all* calls (including magic methods and + chained calls) +* Extension of `call` object to support chained calls and `callargs` for better + comparisons with or without names. `call` object has a `call_list` method for + chained calls +* Added the public `instance` argument to `create_autospec` +* Support for using Java exceptions as a `side_effect` on Jython +* Improved failure messages for `assert_called_with` and + `assert_called_once_with` +* Tuples as well as lists can be used to specify allowed methods for `spec` & + `spec_set` arguments +* BUGFIX: Fixed bug in `patch.multiple` for argument passing when creating + mocks +* Added license file to the distribution + + +2011/07/16 Version 0.8.0 alpha 2 +-------------------------------- + +* `patch.multiple` for doing multiple patches in a single call, using keyword + arguments +* New `new_callable` argument to `patch` and `patch.object` allowing you to + pass in a class or callable object (instead of `MagicMock`) that will be + called to replace the object being patched +* Addition of `NonCallableMock` and `NonCallableMagicMock`, mocks without a + `__call__` method +* Mocks created by `patch` have a `MagicMock` as the `return_value` where a + class is being patched +* `create_autospec` can create non-callable mocks for non-callable objects. + `return_value` mocks of classes will be non-callable unless the class has + a `__call__` method +* `autospec` creates a `MagicMock` without a spec for properties and slot + descriptors, because we don't know the type of object they return +* Removed the "inherit" argument from `create_autospec` +* Calling `stop` on an unstarted patcher fails with a more meaningful error + message +* BUGFIX: an error creating a patch, with nested patch decorators, won't leave + patches in place +* BUGFIX: `__truediv__` and `__rtruediv__` not available as magic methods on + mocks in Python 3 +* BUGFIX: `assert_called_with` / `assert_called_once_with` can be used with + `self` as a keyword argument +* BUGFIX: autospec for functions / methods with an argument named self that + isn't the first argument no longer broken +* BUGFIX: when patching a class with an explicit spec / spec_set (not a + boolean) it applies "spec inheritance" to the return value of the created + mock (the "instance") +* BUGFIX: remove the `__unittest` marker causing traceback truncation + + +2011/06/14 Version 0.8.0 alpha 1 +-------------------------------- + +mock 0.8.0 is the last version that will support Python 2.4. + +* The patchers (`patch`, `patch.object` and `patch.dict`), plus `Mock` and + `MagicMock`, take arbitrary keyword arguments for configuration +* New mock method `configure_mock` for setting attributes and return values / + side effects on the mock and its attributes +* In Python 2.6 or more recent, `dir` on a mock will report all the dynamically + created attributes (or the full list of attributes if there is a spec) as + well as all the mock methods and attributes. +* Module level `FILTER_DIR` added to control whether `dir(mock)` filters + private attributes. `True` by default. Note that `vars(Mock())` can still be + used to get all instance attributes and `dir(type(Mock())` will still return + all the other attributes (irrespective of `FILTER_DIR`) +* `patch` and `patch.object` now create a `MagicMock` instead of a `Mock` by + default +* Added `ANY` for ignoring arguments in `assert_called_with` calls +* Addition of `call` helper object +* Protocol methods on `MagicMock` are magic mocks, and are created lazily on + first lookup. This means the result of calling a protocol method is a + MagicMock instead of a Mock as it was previously +* Added the Mock API (`assert_called_with` etc) to functions created by + `mocksignature` +* Private attributes `_name`, `_methods`, '_children', `_wraps` and `_parent` + (etc) renamed to reduce likelihood of clash with user attributes. +* Implemented auto-speccing (recursive, lazy speccing of mocks with mocked + signatures for functions/methods) + + Limitations: + + - Doesn't mock magic methods or attributes (it creates MagicMocks, so the + magic methods are *there*, they just don't have the signature mocked nor + are attributes followed) + - Doesn't mock function / method attributes + - Uses object traversal on the objects being mocked to determine types - so + properties etc may be triggered + - The return value of mocked classes (the 'instance') has the same call + signature as the class __init__ (as they share the same spec) + + You create auto-specced mocks by passing `autospec=True` to `patch`. + + Note that attributes that are None are special cased and mocked without a + spec (so any attribute / method can be used). This is because None is + typically used as a default value for attributes that may be of some other + type, and as we don't know what type that may be we allow all access. + + Note that the `autospec` option to `patch` obsoletes the `mocksignature` + option. + +* Added the `create_autospec` function for manually creating 'auto-specced' + mocks +* Removal of deprecated `patch_object` + + +2011/05/30 Version 0.7.2 +------------------------ + +* BUGFIX: instances of list subclasses can now be used as mock specs +* BUGFIX: MagicMock equality / inequality protocol methods changed to use the + default equality / inequality. This is done through a `side_effect` on + the mocks used for `__eq__` / `__ne__` + + +2011/05/06 Version 0.7.1 +------------------------ + +Package fixes contributed by Michael Fladischer. No code changes. + +* Include template in package +* Use isolated binaries for the tox tests +* Unset executable bit on docs +* Fix DOS line endings in getting-started.txt + + +2011/03/05 Version 0.7.0 +------------------------ + +No API changes since 0.7.0 rc1. Many documentation changes including a stylish +new `Sphinx theme `_. + +The full set of changes since 0.6.0 are: + +* Python 3 compatibility +* Ability to mock magic methods with `Mock` and addition of `MagicMock` + with pre-created magic methods +* Addition of `mocksignature` and `mocksignature` argument to `patch` and + `patch.object` +* Addition of `patch.dict` for changing dictionaries during a test +* Ability to use `patch`, `patch.object` and `patch.dict` as class decorators +* Renamed ``patch_object`` to `patch.object` (``patch_object`` is + deprecated) +* Addition of soft comparisons: `call_args`, `call_args_list` and `method_calls` + now return tuple-like objects which compare equal even when empty args + or kwargs are skipped +* patchers (`patch`, `patch.object` and `patch.dict`) have start and stop + methods +* Addition of `assert_called_once_with` method +* Mocks can now be named (`name` argument to constructor) and the name is used + in the repr +* repr of a mock with a spec includes the class name of the spec +* `assert_called_with` works with `python -OO` +* New `spec_set` keyword argument to `Mock` and `patch`. If used, + attempting to *set* an attribute on a mock not on the spec will raise an + `AttributeError` +* Mocks created with a spec can now pass `isinstance` tests (`__class__` + returns the type of the spec) +* Added docstrings to all objects +* Improved failure message for `Mock.assert_called_with` when the mock + has not been called at all +* Decorated functions / methods have their docstring and `__module__` + preserved on Python 2.4. +* BUGFIX: `mock.patch` now works correctly with certain types of objects that + proxy attribute access, like the django settings object +* BUGFIX: mocks are now copyable (thanks to Ned Batchelder for reporting and + diagnosing this) +* BUGFIX: `spec=True` works with old style classes +* BUGFIX: ``help(mock)`` works now (on the module). Can no longer use ``__bases__`` + as a valid sentinel name (thanks to Stephen Emslie for reporting and + diagnosing this) +* BUGFIX: ``side_effect`` now works with ``BaseException`` exceptions like + ``KeyboardInterrupt`` +* BUGFIX: `reset_mock` caused infinite recursion when a mock is set as its own + return value +* BUGFIX: patching the same object twice now restores the patches correctly +* with statement tests now skipped on Python 2.4 +* Tests require unittest2 (or unittest2-py3k) to run +* Tested with `tox `_ on Python 2.4 - 3.2, + jython and pypy (excluding 3.0) +* Added 'build_sphinx' command to setup.py (requires setuptools or distribute) + Thanks to Florian Bauer +* Switched from subversion to mercurial for source code control +* `Konrad Delong `_ added as co-maintainer + + +2011/02/16 Version 0.7.0 RC 1 +----------------------------- + +Changes since beta 4: + +* Tested with jython, pypy and Python 3.2 and 3.1 +* Decorated functions / methods have their docstring and `__module__` + preserved on Python 2.4 +* BUGFIX: `mock.patch` now works correctly with certain types of objects that + proxy attribute access, like the django settings object +* BUGFIX: `reset_mock` caused infinite recursion when a mock is set as its own + return value + + +2010/11/12 Version 0.7.0 beta 4 +------------------------------- + +* patchers (`patch`, `patch.object` and `patch.dict`) have start and stop + methods +* Addition of `assert_called_once_with` method +* repr of a mock with a spec includes the class name of the spec +* `assert_called_with` works with `python -OO` +* New `spec_set` keyword argument to `Mock` and `patch`. If used, + attempting to *set* an attribute on a mock not on the spec will raise an + `AttributeError` +* Attributes and return value of a `MagicMock` are `MagicMock` objects +* Attempting to set an unsupported magic method now raises an `AttributeError` +* `patch.dict` works as a class decorator +* Switched from subversion to mercurial for source code control +* BUGFIX: mocks are now copyable (thanks to Ned Batchelder for reporting and + diagnosing this) +* BUGFIX: `spec=True` works with old style classes +* BUGFIX: `mocksignature=True` can now patch instance methods via + `patch.object` + + +2010/09/18 Version 0.7.0 beta 3 +------------------------------- + +* Using spec with :class:`MagicMock` only pre-creates magic methods in the spec +* Setting a magic method on a mock with a ``spec`` can only be done if the + spec has that method +* Mocks can now be named (`name` argument to constructor) and the name is used + in the repr +* `mocksignature` can now be used with classes (signature based on `__init__`) + and callable objects (signature based on `__call__`) +* Mocks created with a spec can now pass `isinstance` tests (`__class__` + returns the type of the spec) +* Default numeric value for MagicMock is 1 rather than zero (because the + MagicMock bool defaults to True and 0 is False) +* Improved failure message for :meth:`~Mock.assert_called_with` when the mock + has not been called at all +* Adding the following to the set of supported magic methods: + + - ``__getformat__`` and ``__setformat__`` + - pickle methods + - ``__trunc__``, ``__ceil__`` and ``__floor__`` + - ``__sizeof__`` + +* Added 'build_sphinx' command to setup.py (requires setuptools or distribute) + Thanks to Florian Bauer +* with statement tests now skipped on Python 2.4 +* Tests require unittest2 to run on Python 2.7 +* Improved several docstrings and documentation + + +2010/06/23 Version 0.7.0 beta 2 +------------------------------- + +* :func:`patch.dict` works as a context manager as well as a decorator +* ``patch.dict`` takes a string to specify dictionary as well as a dictionary + object. If a string is supplied the name specified is imported +* BUGFIX: ``patch.dict`` restores dictionary even when an exception is raised + + +2010/06/22 Version 0.7.0 beta 1 +------------------------------- + +* Addition of :func:`mocksignature` +* Ability to mock magic methods +* Ability to use ``patch`` and ``patch.object`` as class decorators +* Renamed ``patch_object`` to :func:`patch.object` (``patch_object`` is + deprecated) +* Addition of :class:`MagicMock` class with all magic methods pre-created for you +* Python 3 compatibility (tested with 3.2 but should work with 3.0 & 3.1 as + well) +* Addition of :func:`patch.dict` for changing dictionaries during a test +* Addition of ``mocksignature`` argument to ``patch`` and ``patch.object`` +* ``help(mock)`` works now (on the module). Can no longer use ``__bases__`` + as a valid sentinel name (thanks to Stephen Emslie for reporting and + diagnosing this) +* Addition of soft comparisons: `call_args`, `call_args_list` and `method_calls` + now return tuple-like objects which compare equal even when empty args + or kwargs are skipped +* Added docstrings. +* BUGFIX: ``side_effect`` now works with ``BaseException`` exceptions like + ``KeyboardInterrupt`` +* BUGFIX: patching the same object twice now restores the patches correctly +* The tests now require `unittest2 `_ + to run +* `Konrad Delong `_ added as co-maintainer + + +2009/08/22 Version 0.6.0 +------------------------ + +* New test layout compatible with test discovery +* Descriptors (static methods / class methods etc) can now be patched and + restored correctly +* Mocks can raise exceptions when called by setting ``side_effect`` to an + exception class or instance +* Mocks that wrap objects will not pass on calls to the underlying object if + an explicit return_value is set + + +2009/04/17 Version 0.5.0 +------------------------ + +* Made DEFAULT part of the public api. +* Documentation built with Sphinx. +* ``side_effect`` is now called with the same arguments as the mock is called with and + if returns a non-DEFAULT value that is automatically set as the ``mock.return_value``. +* ``wraps`` keyword argument used for wrapping objects (and passing calls through to the wrapped object). +* ``Mock.reset`` renamed to ``Mock.reset_mock``, as reset is a common API name. +* ``patch`` / ``patch_object`` are now context managers and can be used with ``with``. +* A new 'create' keyword argument to patch and patch_object that allows them to patch + (and unpatch) attributes that don't exist. (Potentially unsafe to use - it can allow + you to have tests that pass when they are testing an API that doesn't exist - use at + your own risk!) +* The methods keyword argument to Mock has been removed and merged with spec. The spec + argument can now be a list of methods or an object to take the spec from. +* Nested patches may now be applied in a different order (created mocks passed + in the opposite order). This is actually a bugfix. +* patch and patch_object now take a spec keyword argument. If spec is + passed in as 'True' then the Mock created will take the object it is replacing + as its spec object. If the object being replaced is a class, then the return + value for the mock will also use the class as a spec. +* A Mock created without a spec will not attempt to mock any magic methods / attributes + (they will raise an ``AttributeError`` instead). + + +2008/10/12 Version 0.4.0 +------------------------ + +* Default return value is now a new mock rather than None +* return_value added as a keyword argument to the constructor +* New method 'assert_called_with' +* Added 'side_effect' attribute / keyword argument called when mock is called +* patch decorator split into two decorators: + + - ``patch_object`` which takes an object and an attribute name to patch + (plus optionally a value to patch with which defaults to a mock object) + - ``patch`` which takes a string specifying a target to patch; in the form + 'package.module.Class.attribute'. (plus optionally a value to + patch with which defaults to a mock object) + +* Can now patch objects with ``None`` +* Change to patch for nose compatibility with error reporting in wrapped functions +* Reset no longer clears children / return value etc - it just resets + call count and call args. It also calls reset on all children (and + the return value if it is a mock). + +Thanks to Konrad Delong, Kevin Dangoor and others for patches and suggestions. + + +2007/12/03 Version 0.3.1 +------------------------- + +``patch`` maintains the name of decorated functions for compatibility with nose +test autodiscovery. + +Tests decorated with ``patch`` that use the two argument form (implicit mock +creation) will receive the mock(s) passed in as extra arguments. + +Thanks to Kevin Dangoor for these changes. + + +2007/11/30 Version 0.3.0 +------------------------- + +Removed ``patch_module``. ``patch`` can now take a string as the first +argument for patching modules. + +The third argument to ``patch`` is optional - a mock will be created by +default if it is not passed in. + + +2007/11/21 Version 0.2.1 +------------------------- + +Bug fix, allows reuse of functions decorated with ``patch`` and ``patch_module``. + + +2007/11/20 Version 0.2.0 +------------------------- + +Added ``spec`` keyword argument for creating ``Mock`` objects from a +specification object. + +Added ``patch`` and ``patch_module`` monkey patching decorators. + +Added ``sentinel`` for convenient access to unique objects. + +Distribution includes unit tests. + + +2007/11/19 Version 0.1.0 +------------------------- + +Initial release. + + +TODO and Limitations +==================== + +Contributions, bug reports and comments welcomed! + +Feature requests and bug reports are handled on the issue tracker: + + * `mock issue tracker `_ + +`wraps` is not integrated with magic methods. + +`patch` could auto-do the patching in the constructor and unpatch in the +destructor. This would be useful in itself, but violates TOOWTDI and would be +unsafe for IronPython & PyPy (non-deterministic calling of destructors). +Destructors aren't called in CPython where there are cycles, but a weak +reference with a callback can be used to get round this. + +`Mock` has several attributes. This makes it unsuitable for mocking objects +that use these attribute names. A way round this would be to provide methods +that *hide* these attributes when needed. In 0.8 many, but not all, of these +attributes are renamed to gain a `_mock` prefix, making it less likely that +they will clash. Any outstanding attributes that haven't been modified with +the prefix should be changed. + +If a patch is started using `patch.start` and then not stopped correctly then +the unpatching is not done. Using weak references it would be possible to +detect and fix this when the patch object itself is garbage collected. This +would be tricky to get right though. + +When a `Mock` is created by `patch`, arbitrary keywords can be used to set +attributes. If `patch` is created with a `spec`, and is replacing a class, then +a `return_value` mock is created. The keyword arguments are not applied to the +child mock, but could be. + +When mocking a class with `patch`, passing in `spec=True` or `autospec=True`, +the mock class has an instance created from the same spec. Should this be the +default behaviour for mocks anyway (mock return values inheriting the spec +from their parent), or should it be controlled by an additional keyword +argument (`inherit`) to the Mock constructor? `create_autospec` does this, so +an additional keyword argument to Mock is probably unnecessary. + +The `mocksignature` argument to `patch` with a non `Mock` passed into +`new_callable` will *probably* cause an error. Should it just be invalid? + +Note that `NonCallableMock` and `NonCallableMagicMock` still have the unused +(and unusable) attributes: `return_value`, `side_effect`, `call_count`, +`call_args` and `call_args_list`. These could be removed or raise errors on +getting / setting. They also have the `assert_called_with` and +`assert_called_once_with` methods. Removing these would be pointless as +fetching them would create a mock (attribute) that could be called without +error. + +Some outstanding technical debt. The way autospeccing mocks function +signatures was copied and modified from `mocksignature`. This could all be +refactored into one set of functions instead of two. The way we tell if +patchers are started and if a patcher is being used for a `patch.multiple` +call are both horrible. There are now a host of helper functions that should +be rationalised. (Probably time to split mock into a package instead of a +module.) + +Passing arbitrary keyword arguments to `create_autospec`, or `patch` with +`autospec`, when mocking a *function* works fine. However, the arbitrary +attributes are set on the created mock - but `create_autospec` returns a +real function (which doesn't have those attributes). However, what is the use +case for using autospec to create functions with attributes that don't exist +on the original? + +`mocksignature`, plus the `call_args_list` and `method_calls` attributes of +`Mock` could all be deprecated. diff --git a/python/mock-1.0.0/html/_sources/compare.txt b/python/mock-1.0.0/html/_sources/compare.txt new file mode 100644 index 00000000000..41555308e2f --- /dev/null +++ b/python/mock-1.0.0/html/_sources/compare.txt @@ -0,0 +1,628 @@ +========================= + Mock Library Comparison +========================= + + +.. testsetup:: + + def assertEqual(a, b): + assert a == b, ("%r != %r" % (a, b)) + + def assertRaises(Exc, func): + try: + func() + except Exc: + return + assert False, ("%s not raised" % Exc) + + sys.modules['somemodule'] = somemodule = mock.Mock(name='somemodule') + class SomeException(Exception): + some_method = method1 = method2 = None + some_other_object = SomeObject = SomeException + + +A side-by-side comparison of how to accomplish some basic tasks with mock and +some other popular Python mocking libraries and frameworks. + +These are: + +* `flexmock `_ +* `mox `_ +* `Mocker `_ +* `dingus `_ +* `fudge `_ + +Popular python mocking frameworks not yet represented here include +`MiniMock `_. + +`pMock `_ (last release 2004 and doesn't import +in recent versions of Python) and +`python-mock `_ (last release 2005) are +intentionally omitted. + +.. note:: + + A more up to date, and tested for all mock libraries (only the mock + examples on this page can be executed as doctests) version of this + comparison is maintained by Gary Bernhardt: + + * `Python Mock Library Comparison + `_ + +This comparison is by no means complete, and also may not be fully idiomatic +for all the libraries represented. *Please* contribute corrections, missing +comparisons, or comparisons for additional libraries to the `mock issue +tracker `_. + +This comparison page was originally created by the `Mox project +`_ and then extended for +`flexmock and mock `_ by +Herman Sheremetyev. Dingus examples written by `Gary Bernhadt +`_. fudge examples +provided by `Kumar McMillan `_. + +.. note:: + + The examples tasks here were originally created by Mox which is a mocking + *framework* rather than a library like mock. The tasks shown naturally + exemplify tasks that frameworks are good at and not the ones they make + harder. In particular you can take a `Mock` or `MagicMock` object and use + it in any way you want with no up-front configuration. The same is also + true for Dingus. + + The examples for mock here assume version 0.7.0. + + +Simple fake object +~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock() + >>> my_mock.some_method.return_value = "calculated value" + >>> my_mock.some_attribute = "value" + >>> assertEqual("calculated value", my_mock.some_method()) + >>> assertEqual("value", my_mock.some_attribute) + +:: + + # Flexmock + mock = flexmock(some_method=lambda: "calculated value", some_attribute="value") + assertEqual("calculated value", mock.some_method()) + assertEqual("value", mock.some_attribute) + + # Mox + mock = mox.MockAnything() + mock.some_method().AndReturn("calculated value") + mock.some_attribute = "value" + mox.Replay(mock) + assertEqual("calculated value", mock.some_method()) + assertEqual("value", mock.some_attribute) + + # Mocker + mock = mocker.mock() + mock.some_method() + mocker.result("calculated value") + mocker.replay() + mock.some_attribute = "value" + assertEqual("calculated value", mock.some_method()) + assertEqual("value", mock.some_attribute) + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus(some_attribute="value", + ... some_method__returns="calculated value") + >>> assertEqual("calculated value", my_dingus.some_method()) + >>> assertEqual("value", my_dingus.some_attribute) + +:: + + >>> # fudge + >>> my_fake = (fudge.Fake() + ... .provides('some_method') + ... .returns("calculated value") + ... .has_attr(some_attribute="value")) + ... + >>> assertEqual("calculated value", my_fake.some_method()) + >>> assertEqual("value", my_fake.some_attribute) + + +Simple mock +~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock() + >>> my_mock.some_method.return_value = "value" + >>> assertEqual("value", my_mock.some_method()) + >>> my_mock.some_method.assert_called_once_with() + +:: + + # Flexmock + mock = flexmock() + mock.should_receive("some_method").and_return("value").once + assertEqual("value", mock.some_method()) + + # Mox + mock = mox.MockAnything() + mock.some_method().AndReturn("value") + mox.Replay(mock) + assertEqual("value", mock.some_method()) + mox.Verify(mock) + + # Mocker + mock = mocker.mock() + mock.some_method() + mocker.result("value") + mocker.replay() + assertEqual("value", mock.some_method()) + mocker.verify() + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus(some_method__returns="value") + >>> assertEqual("value", my_dingus.some_method()) + >>> assert my_dingus.some_method.calls().once() + +:: + + >>> # fudge + >>> @fudge.test + ... def test(): + ... my_fake = (fudge.Fake() + ... .expects('some_method') + ... .returns("value") + ... .times_called(1)) + ... + >>> test() + Traceback (most recent call last): + ... + AssertionError: fake:my_fake.some_method() was not called + + +Creating partial mocks +~~~~~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> SomeObject.some_method = mock.Mock(return_value='value') + >>> assertEqual("value", SomeObject.some_method()) + +:: + + # Flexmock + flexmock(SomeObject).should_receive("some_method").and_return('value') + assertEqual("value", mock.some_method()) + + # Mox + mock = mox.MockObject(SomeObject) + mock.some_method().AndReturn("value") + mox.Replay(mock) + assertEqual("value", mock.some_method()) + mox.Verify(mock) + + # Mocker + mock = mocker.mock(SomeObject) + mock.Get() + mocker.result("value") + mocker.replay() + assertEqual("value", mock.some_method()) + mocker.verify() + +:: + + >>> # Dingus + >>> object = SomeObject + >>> object.some_method = dingus.Dingus(return_value="value") + >>> assertEqual("value", object.some_method()) + +:: + + >>> # fudge + >>> fake = fudge.Fake().is_callable().returns("") + >>> with fudge.patched_context(SomeObject, 'some_method', fake): + ... s = SomeObject() + ... assertEqual("", s.some_method()) + ... + + +Ensure calls are made in specific order +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock(spec=SomeObject) + >>> my_mock.method1() + + >>> my_mock.method2() + + >>> assertEqual(my_mock.mock_calls, [call.method1(), call.method2()]) + +:: + + # Flexmock + mock = flexmock(SomeObject) + mock.should_receive('method1').once.ordered.and_return('first thing') + mock.should_receive('method2').once.ordered.and_return('second thing') + + # Mox + mock = mox.MockObject(SomeObject) + mock.method1().AndReturn('first thing') + mock.method2().AndReturn('second thing') + mox.Replay(mock) + mox.Verify(mock) + + # Mocker + mock = mocker.mock() + with mocker.order(): + mock.method1() + mocker.result('first thing') + mock.method2() + mocker.result('second thing') + mocker.replay() + mocker.verify() + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus() + >>> my_dingus.method1() + + >>> my_dingus.method2() + + >>> assertEqual(['method1', 'method2'], [call.name for call in my_dingus.calls]) + +:: + + >>> # fudge + >>> @fudge.test + ... def test(): + ... my_fake = (fudge.Fake() + ... .remember_order() + ... .expects('method1') + ... .expects('method2')) + ... my_fake.method2() + ... my_fake.method1() + ... + >>> test() + Traceback (most recent call last): + ... + AssertionError: Call #1 was fake:my_fake.method2(); Expected: #1 fake:my_fake.method1(), #2 fake:my_fake.method2(), end + + +Raising exceptions +~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock() + >>> my_mock.some_method.side_effect = SomeException("message") + >>> assertRaises(SomeException, my_mock.some_method) + +:: + + # Flexmock + mock = flexmock() + mock.should_receive("some_method").and_raise(SomeException("message")) + assertRaises(SomeException, mock.some_method) + + # Mox + mock = mox.MockAnything() + mock.some_method().AndRaise(SomeException("message")) + mox.Replay(mock) + assertRaises(SomeException, mock.some_method) + mox.Verify(mock) + + # Mocker + mock = mocker.mock() + mock.some_method() + mocker.throw(SomeException("message")) + mocker.replay() + assertRaises(SomeException, mock.some_method) + mocker.verify() + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus() + >>> my_dingus.some_method = dingus.exception_raiser(SomeException) + >>> assertRaises(SomeException, my_dingus.some_method) + +:: + + >>> # fudge + >>> my_fake = (fudge.Fake() + ... .is_callable() + ... .raises(SomeException("message"))) + ... + >>> my_fake() + Traceback (most recent call last): + ... + SomeException: message + + +Override new instances of a class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> with mock.patch('somemodule.Someclass') as MockClass: + ... MockClass.return_value = some_other_object + ... assertEqual(some_other_object, somemodule.Someclass()) + ... + + +:: + + # Flexmock + flexmock(some_module.SomeClass, new_instances=some_other_object) + assertEqual(some_other_object, some_module.SomeClass()) + + # Mox + # (you will probably have mox.Mox() available as self.mox in a real test) + mox.Mox().StubOutWithMock(some_module, 'SomeClass', use_mock_anything=True) + some_module.SomeClass().AndReturn(some_other_object) + mox.ReplayAll() + assertEqual(some_other_object, some_module.SomeClass()) + + # Mocker + instance = mocker.mock() + klass = mocker.replace(SomeClass, spec=None) + klass('expected', 'args') + mocker.result(instance) + +:: + + >>> # Dingus + >>> MockClass = dingus.Dingus(return_value=some_other_object) + >>> with dingus.patch('somemodule.SomeClass', MockClass): + ... assertEqual(some_other_object, somemodule.SomeClass()) + ... + +:: + + >>> # fudge + >>> @fudge.patch('somemodule.SomeClass') + ... def test(FakeClass): + ... FakeClass.is_callable().returns(some_other_object) + ... assertEqual(some_other_object, somemodule.SomeClass()) + ... + >>> test() + + +Call the same method multiple times +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. note:: + + You don't need to do *any* configuration to call `mock.Mock()` methods + multiple times. Attributes like `call_count`, `call_args_list` and + `method_calls` provide various different ways of making assertions about + how the mock was used. + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock() + >>> my_mock.some_method() + + >>> my_mock.some_method() + + >>> assert my_mock.some_method.call_count >= 2 + +:: + + # Flexmock # (verifies that the method gets called at least twice) + flexmock(some_object).should_receive('some_method').at_least.twice + + # Mox + # (does not support variable number of calls, so you need to create a new entry for each explicit call) + mock = mox.MockObject(some_object) + mock.some_method(mox.IgnoreArg(), mox.IgnoreArg()) + mock.some_method(mox.IgnoreArg(), mox.IgnoreArg()) + mox.Replay(mock) + mox.Verify(mock) + + # Mocker + # (TODO) + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus() + >>> my_dingus.some_method() + + >>> my_dingus.some_method() + + >>> assert len(my_dingus.calls('some_method')) == 2 + +:: + + >>> # fudge + >>> @fudge.test + ... def test(): + ... my_fake = fudge.Fake().expects('some_method').times_called(2) + ... my_fake.some_method() + ... + >>> test() + Traceback (most recent call last): + ... + AssertionError: fake:my_fake.some_method() was called 1 time(s). Expected 2. + + +Mock chained methods +~~~~~~~~~~~~~~~~~~~~ + +.. doctest:: + + >>> # mock + >>> my_mock = mock.Mock() + >>> method3 = my_mock.method1.return_value.method2.return_value.method3 + >>> method3.return_value = 'some value' + >>> assertEqual('some value', my_mock.method1().method2().method3(1, 2)) + >>> method3.assert_called_once_with(1, 2) + +:: + + # Flexmock + # (intermediate method calls are automatically assigned to temporary fake objects + # and can be called with any arguments) + flexmock(some_object).should_receive( + 'method1.method2.method3' + ).with_args(arg1, arg2).and_return('some value') + assertEqual('some_value', some_object.method1().method2().method3(arg1, arg2)) + +:: + + # Mox + mock = mox.MockObject(some_object) + mock2 = mox.MockAnything() + mock3 = mox.MockAnything() + mock.method1().AndReturn(mock1) + mock2.method2().AndReturn(mock2) + mock3.method3(arg1, arg2).AndReturn('some_value') + self.mox.ReplayAll() + assertEqual("some_value", some_object.method1().method2().method3(arg1, arg2)) + self.mox.VerifyAll() + + # Mocker + # (TODO) + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus() + >>> method3 = my_dingus.method1.return_value.method2.return_value.method3 + >>> method3.return_value = 'some value' + >>> assertEqual('some value', my_dingus.method1().method2().method3(1, 2)) + >>> assert method3.calls('()', 1, 2).once() + +:: + + >>> # fudge + >>> @fudge.test + ... def test(): + ... my_fake = fudge.Fake() + ... (my_fake + ... .expects('method1') + ... .returns_fake() + ... .expects('method2') + ... .returns_fake() + ... .expects('method3') + ... .with_args(1, 2) + ... .returns('some value')) + ... assertEqual('some value', my_fake.method1().method2().method3(1, 2)) + ... + >>> test() + + +Mocking a context manager +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Examples for mock, Dingus and fudge only (so far): + +.. doctest:: + + >>> # mock + >>> my_mock = mock.MagicMock() + >>> with my_mock: + ... pass + ... + >>> my_mock.__enter__.assert_called_with() + >>> my_mock.__exit__.assert_called_with(None, None, None) + +:: + + + >>> # Dingus (nothing special here; all dinguses are "magic mocks") + >>> my_dingus = dingus.Dingus() + >>> with my_dingus: + ... pass + ... + >>> assert my_dingus.__enter__.calls() + >>> assert my_dingus.__exit__.calls('()', None, None, None) + +:: + + >>> # fudge + >>> my_fake = fudge.Fake().provides('__enter__').provides('__exit__') + >>> with my_fake: + ... pass + ... + + +Mocking the builtin open used as a context manager +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Example for mock only (so far): + +.. doctest:: + + >>> # mock + >>> my_mock = mock.MagicMock() + >>> with mock.patch('__builtin__.open', my_mock): + ... manager = my_mock.return_value.__enter__.return_value + ... manager.read.return_value = 'some data' + ... with open('foo') as h: + ... data = h.read() + ... + >>> data + 'some data' + >>> my_mock.assert_called_once_with('foo') + +*or*: + +.. doctest:: + + >>> # mock + >>> with mock.patch('__builtin__.open') as my_mock: + ... my_mock.return_value.__enter__ = lambda s: s + ... my_mock.return_value.__exit__ = mock.Mock() + ... my_mock.return_value.read.return_value = 'some data' + ... with open('foo') as h: + ... data = h.read() + ... + >>> data + 'some data' + >>> my_mock.assert_called_once_with('foo') + +:: + + >>> # Dingus + >>> my_dingus = dingus.Dingus() + >>> with dingus.patch('__builtin__.open', my_dingus): + ... file_ = open.return_value.__enter__.return_value + ... file_.read.return_value = 'some data' + ... with open('foo') as h: + ... data = f.read() + ... + >>> data + 'some data' + >>> assert my_dingus.calls('()', 'foo').once() + +:: + + >>> # fudge + >>> from contextlib import contextmanager + >>> from StringIO import StringIO + >>> @contextmanager + ... def fake_file(filename): + ... yield StringIO('sekrets') + ... + >>> with fudge.patch('__builtin__.open') as fake_open: + ... fake_open.is_callable().calls(fake_file) + ... with open('/etc/password') as f: + ... data = f.read() + ... + fake:__builtin__.open + >>> data + 'sekrets' \ No newline at end of file diff --git a/python/mock-1.0.0/html/_sources/examples.txt b/python/mock-1.0.0/html/_sources/examples.txt new file mode 100644 index 00000000000..ecb994b156a --- /dev/null +++ b/python/mock-1.0.0/html/_sources/examples.txt @@ -0,0 +1,1063 @@ +.. _further-examples: + +================== + Further Examples +================== + +.. currentmodule:: mock + +.. testsetup:: + + from datetime import date + + BackendProvider = Mock() + sys.modules['mymodule'] = mymodule = Mock(name='mymodule') + + def grob(val): + "First frob and then clear val" + mymodule.frob(val) + val.clear() + + mymodule.frob = lambda val: val + mymodule.grob = grob + mymodule.date = date + + class TestCase(unittest2.TestCase): + def run(self): + result = unittest2.TestResult() + out = unittest2.TestCase.run(self, result) + assert result.wasSuccessful() + + from mock import inPy3k + + + +For comprehensive examples, see the unit tests included in the full source +distribution. + +Here are some more examples for some slightly more advanced scenarios than in +the :ref:`getting started ` guide. + + +Mocking chained calls +===================== + +Mocking chained calls is actually straightforward with mock once you +understand the :attr:`~Mock.return_value` attribute. When a mock is called for +the first time, or you fetch its `return_value` before it has been called, a +new `Mock` is created. + +This means that you can see how the object returned from a call to a mocked +object has been used by interrogating the `return_value` mock: + +.. doctest:: + + >>> mock = Mock() + >>> mock().foo(a=2, b=3) + + >>> mock.return_value.foo.assert_called_with(a=2, b=3) + +From here it is a simple step to configure and then make assertions about +chained calls. Of course another alternative is writing your code in a more +testable way in the first place... + +So, suppose we have some code that looks a little bit like this: + +.. doctest:: + + >>> class Something(object): + ... def __init__(self): + ... self.backend = BackendProvider() + ... def method(self): + ... response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call() + ... # more code + +Assuming that `BackendProvider` is already well tested, how do we test +`method()`? Specifically, we want to test that the code section `# more +code` uses the response object in the correct way. + +As this chain of calls is made from an instance attribute we can monkey patch +the `backend` attribute on a `Something` instance. In this particular case +we are only interested in the return value from the final call to +`start_call` so we don't have much configuration to do. Let's assume the +object it returns is 'file-like', so we'll ensure that our response object +uses the builtin `file` as its `spec`. + +To do this we create a mock instance as our mock backend and create a mock +response object for it. To set the response as the return value for that final +`start_call` we could do this: + + `mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response`. + +We can do that in a slightly nicer way using the :meth:`~Mock.configure_mock` +method to directly set the return value for us: + +.. doctest:: + + >>> something = Something() + >>> mock_response = Mock(spec=file) + >>> mock_backend = Mock() + >>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response} + >>> mock_backend.configure_mock(**config) + +With these we monkey patch the "mock backend" in place and can make the real +call: + +.. doctest:: + + >>> something.backend = mock_backend + >>> something.method() + +Using :attr:`~Mock.mock_calls` we can check the chained call with a single +assert. A chained call is several calls in one line of code, so there will be +several entries in `mock_calls`. We can use :meth:`call.call_list` to create +this list of calls for us: + +.. doctest:: + + >>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call() + >>> call_list = chained.call_list() + >>> assert mock_backend.mock_calls == call_list + + +Partial mocking +=============== + +In some tests I wanted to mock out a call to `datetime.date.today() +`_ to return +a known date, but I didn't want to prevent the code under test from +creating new date objects. Unfortunately `datetime.date` is written in C, and +so I couldn't just monkey-patch out the static `date.today` method. + +I found a simple way of doing this that involved effectively wrapping the date +class with a mock, but passing through calls to the constructor to the real +class (and returning real instances). + +The :func:`patch decorator ` is used here to +mock out the `date` class in the module under test. The :attr:`side_effect` +attribute on the mock date class is then set to a lambda function that returns +a real date. When the mock date class is called a real date will be +constructed and returned by `side_effect`. + +.. doctest:: + + >>> from datetime import date + >>> with patch('mymodule.date') as mock_date: + ... mock_date.today.return_value = date(2010, 10, 8) + ... mock_date.side_effect = lambda *args, **kw: date(*args, **kw) + ... + ... assert mymodule.date.today() == date(2010, 10, 8) + ... assert mymodule.date(2009, 6, 8) == date(2009, 6, 8) + ... + +Note that we don't patch `datetime.date` globally, we patch `date` in the +module that *uses* it. See :ref:`where to patch `. + +When `date.today()` is called a known date is returned, but calls to the +`date(...)` constructor still return normal dates. Without this you can find +yourself having to calculate an expected result using exactly the same +algorithm as the code under test, which is a classic testing anti-pattern. + +Calls to the date constructor are recorded in the `mock_date` attributes +(`call_count` and friends) which may also be useful for your tests. + +An alternative way of dealing with mocking dates, or other builtin classes, +is discussed in `this blog entry +`_. + + +Mocking a Generator Method +========================== + +A Python generator is a function or method that uses the `yield statement +`_ to +return a series of values when iterated over [#]_. + +A generator method / function is called to return the generator object. It is +the generator object that is then iterated over. The protocol method for +iteration is `__iter__ +`_, so we can +mock this using a `MagicMock`. + +Here's an example class with an "iter" method implemented as a generator: + +.. doctest:: + + >>> class Foo(object): + ... def iter(self): + ... for i in [1, 2, 3]: + ... yield i + ... + >>> foo = Foo() + >>> list(foo.iter()) + [1, 2, 3] + + +How would we mock this class, and in particular its "iter" method? + +To configure the values returned from the iteration (implicit in the call to +`list`), we need to configure the object returned by the call to `foo.iter()`. + +.. doctest:: + + >>> mock_foo = MagicMock() + >>> mock_foo.iter.return_value = iter([1, 2, 3]) + >>> list(mock_foo.iter()) + [1, 2, 3] + +.. [#] There are also generator expressions and more `advanced uses + `_ of generators, but we aren't + concerned about them here. A very good introduction to generators and how + powerful they are is: `Generator Tricks for Systems Programmers + `_. + + +Applying the same patch to every test method +============================================ + +If you want several patches in place for multiple test methods the obvious way +is to apply the patch decorators to every method. This can feel like unnecessary +repetition. For Python 2.6 or more recent you can use `patch` (in all its +various forms) as a class decorator. This applies the patches to all test +methods on the class. A test method is identified by methods whose names start +with `test`: + +.. doctest:: + + >>> @patch('mymodule.SomeClass') + ... class MyTest(TestCase): + ... + ... def test_one(self, MockSomeClass): + ... self.assertTrue(mymodule.SomeClass is MockSomeClass) + ... + ... def test_two(self, MockSomeClass): + ... self.assertTrue(mymodule.SomeClass is MockSomeClass) + ... + ... def not_a_test(self): + ... return 'something' + ... + >>> MyTest('test_one').test_one() + >>> MyTest('test_two').test_two() + >>> MyTest('test_two').not_a_test() + 'something' + +An alternative way of managing patches is to use the :ref:`start-and-stop`. +These allow you to move the patching into your `setUp` and `tearDown` methods. + +.. doctest:: + + >>> class MyTest(TestCase): + ... def setUp(self): + ... self.patcher = patch('mymodule.foo') + ... self.mock_foo = self.patcher.start() + ... + ... def test_foo(self): + ... self.assertTrue(mymodule.foo is self.mock_foo) + ... + ... def tearDown(self): + ... self.patcher.stop() + ... + >>> MyTest('test_foo').run() + +If you use this technique you must ensure that the patching is "undone" by +calling `stop`. This can be fiddlier than you might think, because if an +exception is raised in the setUp then tearDown is not called. `unittest2 +`_ cleanup functions make this simpler: + + +.. doctest:: + + >>> class MyTest(TestCase): + ... def setUp(self): + ... patcher = patch('mymodule.foo') + ... self.addCleanup(patcher.stop) + ... self.mock_foo = patcher.start() + ... + ... def test_foo(self): + ... self.assertTrue(mymodule.foo is self.mock_foo) + ... + >>> MyTest('test_foo').run() + + +Mocking Unbound Methods +======================= + +Whilst writing tests today I needed to patch an *unbound method* (patching the +method on the class rather than on the instance). I needed self to be passed +in as the first argument because I want to make asserts about which objects +were calling this particular method. The issue is that you can't patch with a +mock for this, because if you replace an unbound method with a mock it doesn't +become a bound method when fetched from the instance, and so it doesn't get +self passed in. The workaround is to patch the unbound method with a real +function instead. The :func:`patch` decorator makes it so simple to +patch out methods with a mock that having to create a real function becomes a +nuisance. + +If you pass `autospec=True` to patch then it does the patching with a +*real* function object. This function object has the same signature as the one +it is replacing, but delegates to a mock under the hood. You still get your +mock auto-created in exactly the same way as before. What it means though, is +that if you use it to patch out an unbound method on a class the mocked +function will be turned into a bound method if it is fetched from an instance. +It will have `self` passed in as the first argument, which is exactly what I +wanted: + +.. doctest:: + + >>> class Foo(object): + ... def foo(self): + ... pass + ... + >>> with patch.object(Foo, 'foo', autospec=True) as mock_foo: + ... mock_foo.return_value = 'foo' + ... foo = Foo() + ... foo.foo() + ... + 'foo' + >>> mock_foo.assert_called_once_with(foo) + +If we don't use `autospec=True` then the unbound method is patched out +with a Mock instance instead, and isn't called with `self`. + + +Checking multiple calls with mock +================================= + +mock has a nice API for making assertions about how your mock objects are used. + +.. doctest:: + + >>> mock = Mock() + >>> mock.foo_bar.return_value = None + >>> mock.foo_bar('baz', spam='eggs') + >>> mock.foo_bar.assert_called_with('baz', spam='eggs') + +If your mock is only being called once you can use the +:meth:`assert_called_once_with` method that also asserts that the +:attr:`call_count` is one. + +.. doctest:: + + >>> mock.foo_bar.assert_called_once_with('baz', spam='eggs') + >>> mock.foo_bar() + >>> mock.foo_bar.assert_called_once_with('baz', spam='eggs') + Traceback (most recent call last): + ... + AssertionError: Expected to be called once. Called 2 times. + +Both `assert_called_with` and `assert_called_once_with` make assertions about +the *most recent* call. If your mock is going to be called several times, and +you want to make assertions about *all* those calls you can use +:attr:`~Mock.call_args_list`: + +.. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock(1, 2, 3) + >>> mock(4, 5, 6) + >>> mock() + >>> mock.call_args_list + [call(1, 2, 3), call(4, 5, 6), call()] + +The :data:`call` helper makes it easy to make assertions about these calls. You +can build up a list of expected calls and compare it to `call_args_list`. This +looks remarkably similar to the repr of the `call_args_list`: + +.. doctest:: + + >>> expected = [call(1, 2, 3), call(4, 5, 6), call()] + >>> mock.call_args_list == expected + True + + +Coping with mutable arguments +============================= + +Another situation is rare, but can bite you, is when your mock is called with +mutable arguments. `call_args` and `call_args_list` store *references* to the +arguments. If the arguments are mutated by the code under test then you can no +longer make assertions about what the values were when the mock was called. + +Here's some example code that shows the problem. Imagine the following functions +defined in 'mymodule':: + + def frob(val): + pass + + def grob(val): + "First frob and then clear val" + frob(val) + val.clear() + +When we try to test that `grob` calls `frob` with the correct argument look +what happens: + +.. doctest:: + + >>> with patch('mymodule.frob') as mock_frob: + ... val = set([6]) + ... mymodule.grob(val) + ... + >>> val + set([]) + >>> mock_frob.assert_called_with(set([6])) + Traceback (most recent call last): + ... + AssertionError: Expected: ((set([6]),), {}) + Called with: ((set([]),), {}) + +One possibility would be for mock to copy the arguments you pass in. This +could then cause problems if you do assertions that rely on object identity +for equality. + +Here's one solution that uses the :attr:`side_effect` +functionality. If you provide a `side_effect` function for a mock then +`side_effect` will be called with the same args as the mock. This gives us an +opportunity to copy the arguments and store them for later assertions. In this +example I'm using *another* mock to store the arguments so that I can use the +mock methods for doing the assertion. Again a helper function sets this up for +me. + +.. doctest:: + + >>> from copy import deepcopy + >>> from mock import Mock, patch, DEFAULT + >>> def copy_call_args(mock): + ... new_mock = Mock() + ... def side_effect(*args, **kwargs): + ... args = deepcopy(args) + ... kwargs = deepcopy(kwargs) + ... new_mock(*args, **kwargs) + ... return DEFAULT + ... mock.side_effect = side_effect + ... return new_mock + ... + >>> with patch('mymodule.frob') as mock_frob: + ... new_mock = copy_call_args(mock_frob) + ... val = set([6]) + ... mymodule.grob(val) + ... + >>> new_mock.assert_called_with(set([6])) + >>> new_mock.call_args + call(set([6])) + +`copy_call_args` is called with the mock that will be called. It returns a new +mock that we do the assertion on. The `side_effect` function makes a copy of +the args and calls our `new_mock` with the copy. + +.. note:: + + If your mock is only going to be used once there is an easier way of + checking arguments at the point they are called. You can simply do the + checking inside a `side_effect` function. + + .. doctest:: + + >>> def side_effect(arg): + ... assert arg == set([6]) + ... + >>> mock = Mock(side_effect=side_effect) + >>> mock(set([6])) + >>> mock(set()) + Traceback (most recent call last): + ... + AssertionError + +An alternative approach is to create a subclass of `Mock` or `MagicMock` that +copies (using `copy.deepcopy +`_) the arguments. +Here's an example implementation: + +.. doctest:: + + >>> from copy import deepcopy + >>> class CopyingMock(MagicMock): + ... def __call__(self, *args, **kwargs): + ... args = deepcopy(args) + ... kwargs = deepcopy(kwargs) + ... return super(CopyingMock, self).__call__(*args, **kwargs) + ... + >>> c = CopyingMock(return_value=None) + >>> arg = set() + >>> c(arg) + >>> arg.add(1) + >>> c.assert_called_with(set()) + >>> c.assert_called_with(arg) + Traceback (most recent call last): + ... + AssertionError: Expected call: mock(set([1])) + Actual call: mock(set([])) + >>> c.foo + + +When you subclass `Mock` or `MagicMock` all dynamically created attributes, +and the `return_value` will use your subclass automatically. That means all +children of a `CopyingMock` will also have the type `CopyingMock`. + + +Raising exceptions on attribute access +====================================== + +You can use :class:`PropertyMock` to mimic the behaviour of properties. This +includes raising exceptions when an attribute is accessed. + +Here's an example raising a `ValueError` when the 'foo' attribute is accessed: + +.. doctest:: + + >>> m = MagicMock() + >>> p = PropertyMock(side_effect=ValueError) + >>> type(m).foo = p + >>> m.foo + Traceback (most recent call last): + .... + ValueError + +Because every mock object has its own type, a new subclass of whichever mock +class you're using, all mock objects are isolated from each other. You can +safely attach properties (or other descriptors or whatever you want in fact) +to `type(mock)` without affecting other mock objects. + + +Multiple calls with different effects +===================================== + +.. note:: + + In mock 1.0 the handling of iterable `side_effect` was changed. Any + exceptions in the iterable will be raised instead of returned. + +Handling code that needs to behave differently on subsequent calls during the +test can be tricky. For example you may have a function that needs to raise +an exception the first time it is called but returns a response on the second +call (testing retry behaviour). + +One approach is to use a :attr:`side_effect` function that replaces itself. The +first time it is called the `side_effect` sets a new `side_effect` that will +be used for the second call. It then raises an exception: + +.. doctest:: + + >>> def side_effect(*args): + ... def second_call(*args): + ... return 'response' + ... mock.side_effect = second_call + ... raise Exception('boom') + ... + >>> mock = Mock(side_effect=side_effect) + >>> mock('first') + Traceback (most recent call last): + ... + Exception: boom + >>> mock('second') + 'response' + >>> mock.assert_called_with('second') + +Another perfectly valid way would be to pop return values from a list. If the +return value is an exception, raise it instead of returning it: + +.. doctest:: + + >>> returns = [Exception('boom'), 'response'] + >>> def side_effect(*args): + ... result = returns.pop(0) + ... if isinstance(result, Exception): + ... raise result + ... return result + ... + >>> mock = Mock(side_effect=side_effect) + >>> mock('first') + Traceback (most recent call last): + ... + Exception: boom + >>> mock('second') + 'response' + >>> mock.assert_called_with('second') + +Which approach you prefer is a matter of taste. The first approach is actually +a line shorter but maybe the second approach is more readable. + + +Nesting Patches +=============== + +Using patch as a context manager is nice, but if you do multiple patches you +can end up with nested with statements indenting further and further to the +right: + +.. doctest:: + + >>> class MyTest(TestCase): + ... + ... def test_foo(self): + ... with patch('mymodule.Foo') as mock_foo: + ... with patch('mymodule.Bar') as mock_bar: + ... with patch('mymodule.Spam') as mock_spam: + ... assert mymodule.Foo is mock_foo + ... assert mymodule.Bar is mock_bar + ... assert mymodule.Spam is mock_spam + ... + >>> original = mymodule.Foo + >>> MyTest('test_foo').test_foo() + >>> assert mymodule.Foo is original + +With unittest2_ `cleanup` functions and the :ref:`start-and-stop` we can +achieve the same effect without the nested indentation. A simple helper +method, `create_patch`, puts the patch in place and returns the created mock +for us: + +.. doctest:: + + >>> class MyTest(TestCase): + ... + ... def create_patch(self, name): + ... patcher = patch(name) + ... thing = patcher.start() + ... self.addCleanup(patcher.stop) + ... return thing + ... + ... def test_foo(self): + ... mock_foo = self.create_patch('mymodule.Foo') + ... mock_bar = self.create_patch('mymodule.Bar') + ... mock_spam = self.create_patch('mymodule.Spam') + ... + ... assert mymodule.Foo is mock_foo + ... assert mymodule.Bar is mock_bar + ... assert mymodule.Spam is mock_spam + ... + >>> original = mymodule.Foo + >>> MyTest('test_foo').run() + >>> assert mymodule.Foo is original + + +Mocking a dictionary with MagicMock +=================================== + +You may want to mock a dictionary, or other container object, recording all +access to it whilst having it still behave like a dictionary. + +We can do this with :class:`MagicMock`, which will behave like a dictionary, +and using :data:`~Mock.side_effect` to delegate dictionary access to a real +underlying dictionary that is under our control. + +When the `__getitem__` and `__setitem__` methods of our `MagicMock` are called +(normal dictionary access) then `side_effect` is called with the key (and in +the case of `__setitem__` the value too). We can also control what is returned. + +After the `MagicMock` has been used we can use attributes like +:data:`~Mock.call_args_list` to assert about how the dictionary was used: + +.. doctest:: + + >>> my_dict = {'a': 1, 'b': 2, 'c': 3} + >>> def getitem(name): + ... return my_dict[name] + ... + >>> def setitem(name, val): + ... my_dict[name] = val + ... + >>> mock = MagicMock() + >>> mock.__getitem__.side_effect = getitem + >>> mock.__setitem__.side_effect = setitem + +.. note:: + + An alternative to using `MagicMock` is to use `Mock` and *only* provide + the magic methods you specifically want: + + .. doctest:: + + >>> mock = Mock() + >>> mock.__setitem__ = Mock(side_effect=getitem) + >>> mock.__getitem__ = Mock(side_effect=setitem) + + A *third* option is to use `MagicMock` but passing in `dict` as the `spec` + (or `spec_set`) argument so that the `MagicMock` created only has + dictionary magic methods available: + + .. doctest:: + + >>> mock = MagicMock(spec_set=dict) + >>> mock.__getitem__.side_effect = getitem + >>> mock.__setitem__.side_effect = setitem + +With these side effect functions in place, the `mock` will behave like a normal +dictionary but recording the access. It even raises a `KeyError` if you try +to access a key that doesn't exist. + +.. doctest:: + + >>> mock['a'] + 1 + >>> mock['c'] + 3 + >>> mock['d'] + Traceback (most recent call last): + ... + KeyError: 'd' + >>> mock['b'] = 'fish' + >>> mock['d'] = 'eggs' + >>> mock['b'] + 'fish' + >>> mock['d'] + 'eggs' + +After it has been used you can make assertions about the access using the normal +mock methods and attributes: + +.. doctest:: + + >>> mock.__getitem__.call_args_list + [call('a'), call('c'), call('d'), call('b'), call('d')] + >>> mock.__setitem__.call_args_list + [call('b', 'fish'), call('d', 'eggs')] + >>> my_dict + {'a': 1, 'c': 3, 'b': 'fish', 'd': 'eggs'} + + +Mock subclasses and their attributes +==================================== + +There are various reasons why you might want to subclass `Mock`. One reason +might be to add helper methods. Here's a silly example: + +.. doctest:: + + >>> class MyMock(MagicMock): + ... def has_been_called(self): + ... return self.called + ... + >>> mymock = MyMock(return_value=None) + >>> mymock + + >>> mymock.has_been_called() + False + >>> mymock() + >>> mymock.has_been_called() + True + +The standard behaviour for `Mock` instances is that attributes and the return +value mocks are of the same type as the mock they are accessed on. This ensures +that `Mock` attributes are `Mocks` and `MagicMock` attributes are `MagicMocks` +[#]_. So if you're subclassing to add helper methods then they'll also be +available on the attributes and return value mock of instances of your +subclass. + +.. doctest:: + + >>> mymock.foo + + >>> mymock.foo.has_been_called() + False + >>> mymock.foo() + + >>> mymock.foo.has_been_called() + True + +Sometimes this is inconvenient. For example, `one user +`_ is subclassing mock to +created a `Twisted adaptor +`_. +Having this applied to attributes too actually causes errors. + +`Mock` (in all its flavours) uses a method called `_get_child_mock` to create +these "sub-mocks" for attributes and return values. You can prevent your +subclass being used for attributes by overriding this method. The signature is +that it takes arbitrary keyword arguments (`**kwargs`) which are then passed +onto the mock constructor: + +.. doctest:: + + >>> class Subclass(MagicMock): + ... def _get_child_mock(self, **kwargs): + ... return MagicMock(**kwargs) + ... + >>> mymock = Subclass() + >>> mymock.foo + + >>> assert isinstance(mymock, Subclass) + >>> assert not isinstance(mymock.foo, Subclass) + >>> assert not isinstance(mymock(), Subclass) + +.. [#] An exception to this rule are the non-callable mocks. Attributes use the + callable variant because otherwise non-callable mocks couldn't have callable + methods. + + +Mocking imports with patch.dict +=============================== + +One situation where mocking can be hard is where you have a local import inside +a function. These are harder to mock because they aren't using an object from +the module namespace that we can patch out. + +Generally local imports are to be avoided. They are sometimes done to prevent +circular dependencies, for which there is *usually* a much better way to solve +the problem (refactor the code) or to prevent "up front costs" by delaying the +import. This can also be solved in better ways than an unconditional local +import (store the module as a class or module attribute and only do the import +on first use). + +That aside there is a way to use `mock` to affect the results of an import. +Importing fetches an *object* from the `sys.modules` dictionary. Note that it +fetches an *object*, which need not be a module. Importing a module for the +first time results in a module object being put in `sys.modules`, so usually +when you import something you get a module back. This need not be the case +however. + +This means you can use :func:`patch.dict` to *temporarily* put a mock in place +in `sys.modules`. Any imports whilst this patch is active will fetch the mock. +When the patch is complete (the decorated function exits, the with statement +body is complete or `patcher.stop()` is called) then whatever was there +previously will be restored safely. + +Here's an example that mocks out the 'fooble' module. + +.. doctest:: + + >>> mock = Mock() + >>> with patch.dict('sys.modules', {'fooble': mock}): + ... import fooble + ... fooble.blob() + ... + + >>> assert 'fooble' not in sys.modules + >>> mock.blob.assert_called_once_with() + +As you can see the `import fooble` succeeds, but on exit there is no 'fooble' +left in `sys.modules`. + +This also works for the `from module import name` form: + +.. doctest:: + + >>> mock = Mock() + >>> with patch.dict('sys.modules', {'fooble': mock}): + ... from fooble import blob + ... blob.blip() + ... + + >>> mock.blob.blip.assert_called_once_with() + +With slightly more work you can also mock package imports: + +.. doctest:: + + >>> mock = Mock() + >>> modules = {'package': mock, 'package.module': mock.module} + >>> with patch.dict('sys.modules', modules): + ... from package.module import fooble + ... fooble() + ... + + >>> mock.module.fooble.assert_called_once_with() + + +Tracking order of calls and less verbose call assertions +======================================================== + +The :class:`Mock` class allows you to track the *order* of method calls on +your mock objects through the :attr:`~Mock.method_calls` attribute. This +doesn't allow you to track the order of calls between separate mock objects, +however we can use :attr:`~Mock.mock_calls` to achieve the same effect. + +Because mocks track calls to child mocks in `mock_calls`, and accessing an +arbitrary attribute of a mock creates a child mock, we can create our separate +mocks from a parent one. Calls to those child mock will then all be recorded, +in order, in the `mock_calls` of the parent: + +.. doctest:: + + >>> manager = Mock() + >>> mock_foo = manager.foo + >>> mock_bar = manager.bar + + >>> mock_foo.something() + + >>> mock_bar.other.thing() + + + >>> manager.mock_calls + [call.foo.something(), call.bar.other.thing()] + +We can then assert about the calls, including the order, by comparing with +the `mock_calls` attribute on the manager mock: + +.. doctest:: + + >>> expected_calls = [call.foo.something(), call.bar.other.thing()] + >>> manager.mock_calls == expected_calls + True + +If `patch` is creating, and putting in place, your mocks then you can attach +them to a manager mock using the :meth:`~Mock.attach_mock` method. After +attaching calls will be recorded in `mock_calls` of the manager. + +.. doctest:: + + >>> manager = MagicMock() + >>> with patch('mymodule.Class1') as MockClass1: + ... with patch('mymodule.Class2') as MockClass2: + ... manager.attach_mock(MockClass1, 'MockClass1') + ... manager.attach_mock(MockClass2, 'MockClass2') + ... MockClass1().foo() + ... MockClass2().bar() + ... + + + >>> manager.mock_calls + [call.MockClass1(), + call.MockClass1().foo(), + call.MockClass2(), + call.MockClass2().bar()] + +If many calls have been made, but you're only interested in a particular +sequence of them then an alternative is to use the +:meth:`~Mock.assert_has_calls` method. This takes a list of calls (constructed +with the :data:`call` object). If that sequence of calls are in +:attr:`~Mock.mock_calls` then the assert succeeds. + +.. doctest:: + + >>> m = MagicMock() + >>> m().foo().bar().baz() + + >>> m.one().two().three() + + >>> calls = call.one().two().three().call_list() + >>> m.assert_has_calls(calls) + +Even though the chained call `m.one().two().three()` aren't the only calls that +have been made to the mock, the assert still succeeds. + +Sometimes a mock may have several calls made to it, and you are only interested +in asserting about *some* of those calls. You may not even care about the +order. In this case you can pass `any_order=True` to `assert_has_calls`: + +.. doctest:: + + >>> m = MagicMock() + >>> m(1), m.two(2, 3), m.seven(7), m.fifty('50') + (...) + >>> calls = [call.fifty('50'), call(1), call.seven(7)] + >>> m.assert_has_calls(calls, any_order=True) + + +More complex argument matching +============================== + +Using the same basic concept as `ANY` we can implement matchers to do more +complex assertions on objects used as arguments to mocks. + +Suppose we expect some object to be passed to a mock that by default +compares equal based on object identity (which is the Python default for user +defined classes). To use :meth:`~Mock.assert_called_with` we would need to pass +in the exact same object. If we are only interested in some of the attributes +of this object then we can create a matcher that will check these attributes +for us. + +You can see in this example how a 'standard' call to `assert_called_with` isn't +sufficient: + +.. doctest:: + + >>> class Foo(object): + ... def __init__(self, a, b): + ... self.a, self.b = a, b + ... + >>> mock = Mock(return_value=None) + >>> mock(Foo(1, 2)) + >>> mock.assert_called_with(Foo(1, 2)) + Traceback (most recent call last): + ... + AssertionError: Expected: call(<__main__.Foo object at 0x...>) + Actual call: call(<__main__.Foo object at 0x...>) + +A comparison function for our `Foo` class might look something like this: + +.. doctest:: + + >>> def compare(self, other): + ... if not type(self) == type(other): + ... return False + ... if self.a != other.a: + ... return False + ... if self.b != other.b: + ... return False + ... return True + ... + +And a matcher object that can use comparison functions like this for its +equality operation would look something like this: + +.. doctest:: + + >>> class Matcher(object): + ... def __init__(self, compare, some_obj): + ... self.compare = compare + ... self.some_obj = some_obj + ... def __eq__(self, other): + ... return self.compare(self.some_obj, other) + ... + +Putting all this together: + +.. doctest:: + + >>> match_foo = Matcher(compare, Foo(1, 2)) + >>> mock.assert_called_with(match_foo) + +The `Matcher` is instantiated with our compare function and the `Foo` object +we want to compare against. In `assert_called_with` the `Matcher` equality +method will be called, which compares the object the mock was called with +against the one we created our matcher with. If they match then +`assert_called_with` passes, and if they don't an `AssertionError` is raised: + +.. doctest:: + + >>> match_wrong = Matcher(compare, Foo(3, 4)) + >>> mock.assert_called_with(match_wrong) + Traceback (most recent call last): + ... + AssertionError: Expected: ((,), {}) + Called with: ((,), {}) + +With a bit of tweaking you could have the comparison function raise the +`AssertionError` directly and provide a more useful failure message. + +As of version 1.5, the Python testing library `PyHamcrest +`_ provides similar functionality, +that may be useful here, in the form of its equality matcher +(`hamcrest.library.integration.match_equality +`_). + + +Less verbose configuration of mock objects +========================================== + +This recipe, for easier configuration of mock objects, is now part of `Mock`. +See the :meth:`~Mock.configure_mock` method. + + +Matching any argument in assertions +=================================== + +This example is now built in to mock. See :data:`ANY`. + + +Mocking Properties +================== + +This example is now built in to mock. See :class:`PropertyMock`. + + +Mocking open +============ + +This example is now built in to mock. See :func:`mock_open`. + + +Mocks without some attributes +============================= + +This example is now built in to mock. See :ref:`deleting-attributes`. diff --git a/python/mock-1.0.0/html/_sources/getting-started.txt b/python/mock-1.0.0/html/_sources/getting-started.txt new file mode 100644 index 00000000000..1b5d289ebe8 --- /dev/null +++ b/python/mock-1.0.0/html/_sources/getting-started.txt @@ -0,0 +1,479 @@ +=========================== + Getting Started with Mock +=========================== + +.. _getting-started: + +.. index:: Getting Started + +.. testsetup:: + + class SomeClass(object): + static_method = None + class_method = None + attribute = None + + sys.modules['package'] = package = Mock(name='package') + sys.modules['package.module'] = module = package.module + sys.modules['module'] = package.module + + +Using Mock +========== + +Mock Patching Methods +--------------------- + +Common uses for :class:`Mock` objects include: + +* Patching methods +* Recording method calls on objects + +You might want to replace a method on an object to check that +it is called with the correct arguments by another part of the system: + +.. doctest:: + + >>> real = SomeClass() + >>> real.method = MagicMock(name='method') + >>> real.method(3, 4, 5, key='value') + + +Once our mock has been used (`real.method` in this example) it has methods +and attributes that allow you to make assertions about how it has been used. + +.. note:: + + In most of these examples the :class:`Mock` and :class:`MagicMock` classes + are interchangeable. As the `MagicMock` is the more capable class it makes + a sensible one to use by default. + +Once the mock has been called its :attr:`~Mock.called` attribute is set to +`True`. More importantly we can use the :meth:`~Mock.assert_called_with` or +:meth:`~Mock.assert_called_once_with` method to check that it was called with +the correct arguments. + +This example tests that calling `ProductionClass().method` results in a call to +the `something` method: + +.. doctest:: + + >>> from mock import MagicMock + >>> class ProductionClass(object): + ... def method(self): + ... self.something(1, 2, 3) + ... def something(self, a, b, c): + ... pass + ... + >>> real = ProductionClass() + >>> real.something = MagicMock() + >>> real.method() + >>> real.something.assert_called_once_with(1, 2, 3) + + + +Mock for Method Calls on an Object +---------------------------------- + +In the last example we patched a method directly on an object to check that it +was called correctly. Another common use case is to pass an object into a +method (or some part of the system under test) and then check that it is used +in the correct way. + +The simple `ProductionClass` below has a `closer` method. If it is called with +an object then it calls `close` on it. + +.. doctest:: + + >>> class ProductionClass(object): + ... def closer(self, something): + ... something.close() + ... + +So to test it we need to pass in an object with a `close` method and check +that it was called correctly. + +.. doctest:: + + >>> real = ProductionClass() + >>> mock = Mock() + >>> real.closer(mock) + >>> mock.close.assert_called_with() + +We don't have to do any work to provide the 'close' method on our mock. +Accessing close creates it. So, if 'close' hasn't already been called then +accessing it in the test will create it, but :meth:`~Mock.assert_called_with` +will raise a failure exception. + + +Mocking Classes +--------------- + +A common use case is to mock out classes instantiated by your code under test. +When you patch a class, then that class is replaced with a mock. Instances +are created by *calling the class*. This means you access the "mock instance" +by looking at the return value of the mocked class. + +In the example below we have a function `some_function` that instantiates `Foo` +and calls a method on it. The call to `patch` replaces the class `Foo` with a +mock. The `Foo` instance is the result of calling the mock, so it is configured +by modifying the mock :attr:`~Mock.return_value`. + +.. doctest:: + + >>> def some_function(): + ... instance = module.Foo() + ... return instance.method() + ... + >>> with patch('module.Foo') as mock: + ... instance = mock.return_value + ... instance.method.return_value = 'the result' + ... result = some_function() + ... assert result == 'the result' + + +Naming your mocks +----------------- + +It can be useful to give your mocks a name. The name is shown in the repr of +the mock and can be helpful when the mock appears in test failure messages. The +name is also propagated to attributes or methods of the mock: + +.. doctest:: + + >>> mock = MagicMock(name='foo') + >>> mock + + >>> mock.method + + + +Tracking all Calls +------------------ + +Often you want to track more than a single call to a method. The +:attr:`~Mock.mock_calls` attribute records all calls +to child attributes of the mock - and also to their children. + +.. doctest:: + + >>> mock = MagicMock() + >>> mock.method() + + >>> mock.attribute.method(10, x=53) + + >>> mock.mock_calls + [call.method(), call.attribute.method(10, x=53)] + +If you make an assertion about `mock_calls` and any unexpected methods +have been called, then the assertion will fail. This is useful because as well +as asserting that the calls you expected have been made, you are also checking +that they were made in the right order and with no additional calls: + +You use the :data:`call` object to construct lists for comparing with +`mock_calls`: + +.. doctest:: + + >>> expected = [call.method(), call.attribute.method(10, x=53)] + >>> mock.mock_calls == expected + True + + +Setting Return Values and Attributes +------------------------------------ + +Setting the return values on a mock object is trivially easy: + +.. doctest:: + + >>> mock = Mock() + >>> mock.return_value = 3 + >>> mock() + 3 + +Of course you can do the same for methods on the mock: + +.. doctest:: + + >>> mock = Mock() + >>> mock.method.return_value = 3 + >>> mock.method() + 3 + +The return value can also be set in the constructor: + +.. doctest:: + + >>> mock = Mock(return_value=3) + >>> mock() + 3 + +If you need an attribute setting on your mock, just do it: + +.. doctest:: + + >>> mock = Mock() + >>> mock.x = 3 + >>> mock.x + 3 + +Sometimes you want to mock up a more complex situation, like for example +`mock.connection.cursor().execute("SELECT 1")`. If we wanted this call to +return a list, then we have to configure the result of the nested call. + +We can use :data:`call` to construct the set of calls in a "chained call" like +this for easy assertion afterwards: + + +.. doctest:: + + >>> mock = Mock() + >>> cursor = mock.connection.cursor.return_value + >>> cursor.execute.return_value = ['foo'] + >>> mock.connection.cursor().execute("SELECT 1") + ['foo'] + >>> expected = call.connection.cursor().execute("SELECT 1").call_list() + >>> mock.mock_calls + [call.connection.cursor(), call.connection.cursor().execute('SELECT 1')] + >>> mock.mock_calls == expected + True + +It is the call to `.call_list()` that turns our call object into a list of +calls representing the chained calls. + + + +Raising exceptions with mocks +----------------------------- + +A useful attribute is :attr:`~Mock.side_effect`. If you set this to an +exception class or instance then the exception will be raised when the mock +is called. + +.. doctest:: + + >>> mock = Mock(side_effect=Exception('Boom!')) + >>> mock() + Traceback (most recent call last): + ... + Exception: Boom! + + +Side effect functions and iterables +----------------------------------- + +`side_effect` can also be set to a function or an iterable. The use case for +`side_effect` as an iterable is where your mock is going to be called several +times, and you want each call to return a different value. When you set +`side_effect` to an iterable every call to the mock returns the next value +from the iterable: + +.. doctest:: + + >>> mock = MagicMock(side_effect=[4, 5, 6]) + >>> mock() + 4 + >>> mock() + 5 + >>> mock() + 6 + + +For more advanced use cases, like dynamically varying the return values +depending on what the mock is called with, `side_effect` can be a function. +The function will be called with the same arguments as the mock. Whatever the +function returns is what the call returns: + +.. doctest:: + + >>> vals = {(1, 2): 1, (2, 3): 2} + >>> def side_effect(*args): + ... return vals[args] + ... + >>> mock = MagicMock(side_effect=side_effect) + >>> mock(1, 2) + 1 + >>> mock(2, 3) + 2 + + +Creating a Mock from an Existing Object +--------------------------------------- + +One problem with over use of mocking is that it couples your tests to the +implementation of your mocks rather than your real code. Suppose you have a +class that implements `some_method`. In a test for another class, you +provide a mock of this object that *also* provides `some_method`. If later +you refactor the first class, so that it no longer has `some_method` - then +your tests will continue to pass even though your code is now broken! + +`Mock` allows you to provide an object as a specification for the mock, +using the `spec` keyword argument. Accessing methods / attributes on the +mock that don't exist on your specification object will immediately raise an +attribute error. If you change the implementation of your specification, then +tests that use that class will start failing immediately without you having to +instantiate the class in those tests. + +.. doctest:: + + >>> mock = Mock(spec=SomeClass) + >>> mock.old_method() + Traceback (most recent call last): + ... + AttributeError: object has no attribute 'old_method' + +If you want a stronger form of specification that prevents the setting +of arbitrary attributes as well as the getting of them then you can use +`spec_set` instead of `spec`. + + + +Patch Decorators +================ + +.. note:: + + With `patch` it matters that you patch objects in the namespace where they + are looked up. This is normally straightforward, but for a quick guide + read :ref:`where to patch `. + + +A common need in tests is to patch a class attribute or a module attribute, +for example patching a builtin or patching a class in a module to test that it +is instantiated. Modules and classes are effectively global, so patching on +them has to be undone after the test or the patch will persist into other +tests and cause hard to diagnose problems. + +mock provides three convenient decorators for this: `patch`, `patch.object` and +`patch.dict`. `patch` takes a single string, of the form +`package.module.Class.attribute` to specify the attribute you are patching. It +also optionally takes a value that you want the attribute (or class or +whatever) to be replaced with. 'patch.object' takes an object and the name of +the attribute you would like patched, plus optionally the value to patch it +with. + +`patch.object`: + +.. doctest:: + + >>> original = SomeClass.attribute + >>> @patch.object(SomeClass, 'attribute', sentinel.attribute) + ... def test(): + ... assert SomeClass.attribute == sentinel.attribute + ... + >>> test() + >>> assert SomeClass.attribute == original + + >>> @patch('package.module.attribute', sentinel.attribute) + ... def test(): + ... from package.module import attribute + ... assert attribute is sentinel.attribute + ... + >>> test() + +If you are patching a module (including `__builtin__`) then use `patch` +instead of `patch.object`: + +.. doctest:: + + >>> mock = MagicMock(return_value = sentinel.file_handle) + >>> with patch('__builtin__.open', mock): + ... handle = open('filename', 'r') + ... + >>> mock.assert_called_with('filename', 'r') + >>> assert handle == sentinel.file_handle, "incorrect file handle returned" + +The module name can be 'dotted', in the form `package.module` if needed: + +.. doctest:: + + >>> @patch('package.module.ClassName.attribute', sentinel.attribute) + ... def test(): + ... from package.module import ClassName + ... assert ClassName.attribute == sentinel.attribute + ... + >>> test() + +A nice pattern is to actually decorate test methods themselves: + +.. doctest:: + + >>> class MyTest(unittest2.TestCase): + ... @patch.object(SomeClass, 'attribute', sentinel.attribute) + ... def test_something(self): + ... self.assertEqual(SomeClass.attribute, sentinel.attribute) + ... + >>> original = SomeClass.attribute + >>> MyTest('test_something').test_something() + >>> assert SomeClass.attribute == original + +If you want to patch with a Mock, you can use `patch` with only one argument +(or `patch.object` with two arguments). The mock will be created for you and +passed into the test function / method: + +.. doctest:: + + >>> class MyTest(unittest2.TestCase): + ... @patch.object(SomeClass, 'static_method') + ... def test_something(self, mock_method): + ... SomeClass.static_method() + ... mock_method.assert_called_with() + ... + >>> MyTest('test_something').test_something() + +You can stack up multiple patch decorators using this pattern: + +.. doctest:: + + >>> class MyTest(unittest2.TestCase): + ... @patch('package.module.ClassName1') + ... @patch('package.module.ClassName2') + ... def test_something(self, MockClass2, MockClass1): + ... self.assertTrue(package.module.ClassName1 is MockClass1) + ... self.assertTrue(package.module.ClassName2 is MockClass2) + ... + >>> MyTest('test_something').test_something() + +When you nest patch decorators the mocks are passed in to the decorated +function in the same order they applied (the normal *python* order that +decorators are applied). This means from the bottom up, so in the example +above the mock for `test_module.ClassName2` is passed in first. + +There is also :func:`patch.dict` for setting values in a dictionary just +during a scope and restoring the dictionary to its original state when the test +ends: + +.. doctest:: + + >>> foo = {'key': 'value'} + >>> original = foo.copy() + >>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True): + ... assert foo == {'newkey': 'newvalue'} + ... + >>> assert foo == original + +`patch`, `patch.object` and `patch.dict` can all be used as context managers. + +Where you use `patch` to create a mock for you, you can get a reference to the +mock using the "as" form of the with statement: + +.. doctest:: + + >>> class ProductionClass(object): + ... def method(self): + ... pass + ... + >>> with patch.object(ProductionClass, 'method') as mock_method: + ... mock_method.return_value = None + ... real = ProductionClass() + ... real.method(1, 2, 3) + ... + >>> mock_method.assert_called_with(1, 2, 3) + + +As an alternative `patch`, `patch.object` and `patch.dict` can be used as +class decorators. When used in this way it is the same as applying the +decorator indvidually to every method whose name starts with "test". + +For some more advanced examples, see the :ref:`further-examples` page. diff --git a/python/mock-1.0.0/html/_sources/index.txt b/python/mock-1.0.0/html/_sources/index.txt new file mode 100644 index 00000000000..7e4a8daca65 --- /dev/null +++ b/python/mock-1.0.0/html/_sources/index.txt @@ -0,0 +1,411 @@ +==================================== + Mock - Mocking and Testing Library +==================================== + +.. currentmodule:: mock + +:Author: `Michael Foord + `_ +:Version: |release| +:Date: 2012/10/07 +:Homepage: `Mock Homepage`_ +:Download: `Mock on PyPI`_ +:Documentation: `PDF Documentation + `_ +:License: `BSD License`_ +:Support: `Mailing list (testing-in-python@lists.idyll.org) + `_ +:Issue tracker: `Google code project + `_ + +.. _Mock Homepage: http://www.voidspace.org.uk/python/mock/ +.. _BSD License: http://www.voidspace.org.uk/python/license.shtml + + +.. currentmodule:: mock + +.. module:: mock + :synopsis: Mock object and testing library. + +.. index:: introduction + +mock is a library for testing in Python. It allows you to replace parts of +your system under test with mock objects and make assertions about how they +have been used. + +mock is now part of the Python standard library, available as `unittest.mock +`_ +in Python 3.3 onwards. + +mock provides a core :class:`Mock` class removing the need to create a host +of stubs throughout your test suite. After performing an action, you can make +assertions about which methods / attributes were used and arguments they were +called with. You can also specify return values and set needed attributes in +the normal way. + +Additionally, mock provides a :func:`patch` decorator that handles patching +module and class level attributes within the scope of a test, along with +:const:`sentinel` for creating unique objects. See the `quick guide`_ for +some examples of how to use :class:`Mock`, :class:`MagicMock` and +:func:`patch`. + +Mock is very easy to use and is designed for use with +`unittest `_. Mock is based on +the 'action -> assertion' pattern instead of `'record -> replay'` used by many +mocking frameworks. + +mock is tested on Python versions 2.4-2.7, Python 3 plus the latest versions of +Jython and PyPy. + + +.. testsetup:: + + class ProductionClass(object): + def method(self, *args): + pass + + module = sys.modules['module'] = ProductionClass + ProductionClass.ClassName1 = ProductionClass + ProductionClass.ClassName2 = ProductionClass + + + +API Documentation +================= + +.. toctree:: + :maxdepth: 2 + + mock + patch + helpers + sentinel + magicmock + + +User Guide +========== + +.. toctree:: + :maxdepth: 2 + + getting-started + examples + compare + changelog + + +.. index:: installing + +Installing +========== + +The current version is |release|. Mock is stable and widely used. If you do +find any bugs, or have suggestions for improvements / extensions +then please contact us. + +* `mock on PyPI `_ +* `mock documentation as PDF + `_ +* `Google Code Home & Mercurial Repository `_ + +.. index:: repository +.. index:: hg + +You can checkout the latest development version from the Google Code Mercurial +repository with the following command: + + ``hg clone https://mock.googlecode.com/hg/ mock`` + + +.. index:: pip +.. index:: easy_install +.. index:: setuptools + +If you have pip, setuptools or distribute you can install mock with: + + | ``easy_install -U mock`` + | ``pip install -U mock`` + +Alternatively you can download the mock distribution from PyPI and after +unpacking run: + + ``python setup.py install`` + + +Quick Guide +=========== + +:class:`Mock` and :class:`MagicMock` objects create all attributes and +methods as you access them and store details of how they have been used. You +can configure them, to specify return values or limit what attributes are +available, and then make assertions about how they have been used: + +.. doctest:: + + >>> from mock import MagicMock + >>> thing = ProductionClass() + >>> thing.method = MagicMock(return_value=3) + >>> thing.method(3, 4, 5, key='value') + 3 + >>> thing.method.assert_called_with(3, 4, 5, key='value') + +:attr:`side_effect` allows you to perform side effects, including raising an +exception when a mock is called: + +.. doctest:: + + >>> mock = Mock(side_effect=KeyError('foo')) + >>> mock() + Traceback (most recent call last): + ... + KeyError: 'foo' + + >>> values = {'a': 1, 'b': 2, 'c': 3} + >>> def side_effect(arg): + ... return values[arg] + ... + >>> mock.side_effect = side_effect + >>> mock('a'), mock('b'), mock('c') + (1, 2, 3) + >>> mock.side_effect = [5, 4, 3, 2, 1] + >>> mock(), mock(), mock() + (5, 4, 3) + +Mock has many other ways you can configure it and control its behaviour. For +example the `spec` argument configures the mock to take its specification +from another object. Attempting to access attributes or methods on the mock +that don't exist on the spec will fail with an `AttributeError`. + +The :func:`patch` decorator / context manager makes it easy to mock classes or +objects in a module under test. The object you specify will be replaced with a +mock (or other object) during the test and restored when the test ends: + +.. doctest:: + + >>> from mock import patch + >>> @patch('module.ClassName2') + ... @patch('module.ClassName1') + ... def test(MockClass1, MockClass2): + ... module.ClassName1() + ... module.ClassName2() + + ... assert MockClass1 is module.ClassName1 + ... assert MockClass2 is module.ClassName2 + ... assert MockClass1.called + ... assert MockClass2.called + ... + >>> test() + +.. note:: + + When you nest patch decorators the mocks are passed in to the decorated + function in the same order they applied (the normal *python* order that + decorators are applied). This means from the bottom up, so in the example + above the mock for `module.ClassName1` is passed in first. + + With `patch` it matters that you patch objects in the namespace where they + are looked up. This is normally straightforward, but for a quick guide + read :ref:`where to patch `. + +As well as a decorator `patch` can be used as a context manager in a with +statement: + +.. doctest:: + + >>> with patch.object(ProductionClass, 'method', return_value=None) as mock_method: + ... thing = ProductionClass() + ... thing.method(1, 2, 3) + ... + >>> mock_method.assert_called_once_with(1, 2, 3) + + +There is also :func:`patch.dict` for setting values in a dictionary just +during a scope and restoring the dictionary to its original state when the test +ends: + +.. doctest:: + + >>> foo = {'key': 'value'} + >>> original = foo.copy() + >>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True): + ... assert foo == {'newkey': 'newvalue'} + ... + >>> assert foo == original + +Mock supports the mocking of Python :ref:`magic methods `. The +easiest way of using magic methods is with the :class:`MagicMock` class. It +allows you to do things like: + +.. doctest:: + + >>> mock = MagicMock() + >>> mock.__str__.return_value = 'foobarbaz' + >>> str(mock) + 'foobarbaz' + >>> mock.__str__.assert_called_with() + +Mock allows you to assign functions (or other Mock instances) to magic methods +and they will be called appropriately. The `MagicMock` class is just a Mock +variant that has all of the magic methods pre-created for you (well, all the +useful ones anyway). + +The following is an example of using magic methods with the ordinary Mock +class: + +.. doctest:: + + >>> mock = Mock() + >>> mock.__str__ = Mock(return_value='wheeeeee') + >>> str(mock) + 'wheeeeee' + +For ensuring that the mock objects in your tests have the same api as the +objects they are replacing, you can use :ref:`auto-speccing `. +Auto-speccing can be done through the `autospec` argument to patch, or the +:func:`create_autospec` function. Auto-speccing creates mock objects that +have the same attributes and methods as the objects they are replacing, and +any functions and methods (including constructors) have the same call +signature as the real object. + +This ensures that your mocks will fail in the same way as your production +code if they are used incorrectly: + +.. doctest:: + + >>> from mock import create_autospec + >>> def function(a, b, c): + ... pass + ... + >>> mock_function = create_autospec(function, return_value='fishy') + >>> mock_function(1, 2, 3) + 'fishy' + >>> mock_function.assert_called_once_with(1, 2, 3) + >>> mock_function('wrong arguments') + Traceback (most recent call last): + ... + TypeError: () takes exactly 3 arguments (1 given) + +`create_autospec` can also be used on classes, where it copies the signature of +the `__init__` method, and on callable objects where it copies the signature of +the `__call__` method. + + +.. index:: references +.. index:: articles + +References +========== + +Articles, blog entries and other stuff related to testing with Mock: + +* `Imposing a No DB Discipline on Django unit tests + `_ +* `mock-django: tools for mocking the Django ORM and models + `_ +* `PyCon 2011 Video: Testing with mock `_ +* `Mock objects in Python + `_ +* `Python: Injecting Mock Objects for Powerful Testing + `_ +* `Python Mock: How to assert a substring of logger output + `_ +* `Mocking Django `_ +* `Mocking dates and other classes that can't be modified + `_ +* `Mock recipes `_ +* `Mockity mock mock - some love for the mock module + `_ +* `Coverage and Mock (with django) + `_ +* `Python Unit Testing with Mock `_ +* `Getting started with Python Mock + `_ +* `Smart Parameter Checks with mock + `_ +* `Python mock testing techniques and tools + `_ +* `How To Test Django Template Tags + `_ +* `A presentation on Unit Testing with Mock + `_ +* `Mocking with Django and Google AppEngine + `_ + + +.. index:: tests +.. index:: unittest2 + +Tests +===== + +Mock uses `unittest2 `_ for its own +test suite. In order to run it, use the `unit2` script that comes with +`unittest2` module on a checkout of the source repository: + + `unit2 discover` + +If you have `setuptools `_ as well as +unittest2 you can run: + + ``python setup.py test`` + +On Python 3.2 you can use ``unittest`` module from the standard library. + + ``python3.2 -m unittest discover`` + +.. index:: Python 3 + +On Python 3 the tests for unicode are skipped as they are not relevant. On +Python 2.4 tests that use the with statements are skipped as the with statement +is invalid syntax on Python 2.4. + + +.. index:: older versions + +Older Versions +============== + +Documentation for older versions of mock: + +* `mock 0.8 `_ +* `mock 0.7 `_ +* `mock 0.6 `_ + +Docs from the in-development version of `mock` can be found at +`mock.readthedocs.org `_. + + +Terminology +=========== + +Terminology for objects used to replace other ones can be confusing. Terms +like double, fake, mock, stub, and spy are all used with varying meanings. + +In `classic mock terminology +`_ +:class:`mock.Mock` is a `spy `_ that +allows for *post-mortem* examination. This is what I call the "action -> +assertion" [#]_ pattern of testing. + +I'm not however a fan of this "statically typed mocking terminology" +promulgated by `Martin Fowler +`_. It confuses usage +patterns with implementation and prevents you from using natural terminology +when discussing mocking. + +I much prefer duck typing, if an object used in your test suite looks like a +mock object and quacks like a mock object then it's fine to call it a mock, no +matter what the implementation looks like. + +This terminology is perhaps more useful in less capable languages where +different usage patterns will *require* different implementations. +`mock.Mock()` is capable of being used in most of the different roles +described by Fowler, except (annoyingly / frustratingly / ironically) a Mock +itself! + +How about a simpler definition: a "mock object" is an object used to replace a +real one in a system under test. + +.. [#] This pattern is called "AAA" by some members of the testing community; + "Arrange - Act - Assert". diff --git a/python/mock-1.0.0/html/_sources/magicmock.txt b/python/mock-1.0.0/html/_sources/magicmock.txt new file mode 100644 index 00000000000..42b2ed9db10 --- /dev/null +++ b/python/mock-1.0.0/html/_sources/magicmock.txt @@ -0,0 +1,258 @@ + +.. currentmodule:: mock + + +.. _magic-methods: + +Mocking Magic Methods +===================== + +.. currentmodule:: mock + +:class:`Mock` supports mocking `magic methods +`_. This allows mock +objects to replace containers or other objects that implement Python +protocols. + +Because magic methods are looked up differently from normal methods [#]_, this +support has been specially implemented. This means that only specific magic +methods are supported. The supported list includes *almost* all of them. If +there are any missing that you need please let us know! + +You mock magic methods by setting the method you are interested in to a function +or a mock instance. If you are using a function then it *must* take ``self`` as +the first argument [#]_. + +.. doctest:: + + >>> def __str__(self): + ... return 'fooble' + ... + >>> mock = Mock() + >>> mock.__str__ = __str__ + >>> str(mock) + 'fooble' + + >>> mock = Mock() + >>> mock.__str__ = Mock() + >>> mock.__str__.return_value = 'fooble' + >>> str(mock) + 'fooble' + + >>> mock = Mock() + >>> mock.__iter__ = Mock(return_value=iter([])) + >>> list(mock) + [] + +One use case for this is for mocking objects used as context managers in a +`with` statement: + +.. doctest:: + + >>> mock = Mock() + >>> mock.__enter__ = Mock(return_value='foo') + >>> mock.__exit__ = Mock(return_value=False) + >>> with mock as m: + ... assert m == 'foo' + ... + >>> mock.__enter__.assert_called_with() + >>> mock.__exit__.assert_called_with(None, None, None) + +Calls to magic methods do not appear in :attr:`~Mock.method_calls`, but they +are recorded in :attr:`~Mock.mock_calls`. + +.. note:: + + If you use the `spec` keyword argument to create a mock then attempting to + set a magic method that isn't in the spec will raise an `AttributeError`. + +The full list of supported magic methods is: + +* ``__hash__``, ``__sizeof__``, ``__repr__`` and ``__str__`` +* ``__dir__``, ``__format__`` and ``__subclasses__`` +* ``__floor__``, ``__trunc__`` and ``__ceil__`` +* Comparisons: ``__cmp__``, ``__lt__``, ``__gt__``, ``__le__``, ``__ge__``, + ``__eq__`` and ``__ne__`` +* Container methods: ``__getitem__``, ``__setitem__``, ``__delitem__``, + ``__contains__``, ``__len__``, ``__iter__``, ``__getslice__``, + ``__setslice__``, ``__reversed__`` and ``__missing__`` +* Context manager: ``__enter__`` and ``__exit__`` +* Unary numeric methods: ``__neg__``, ``__pos__`` and ``__invert__`` +* The numeric methods (including right hand and in-place variants): + ``__add__``, ``__sub__``, ``__mul__``, ``__div__``, + ``__floordiv__``, ``__mod__``, ``__divmod__``, ``__lshift__``, + ``__rshift__``, ``__and__``, ``__xor__``, ``__or__``, and ``__pow__`` +* Numeric conversion methods: ``__complex__``, ``__int__``, ``__float__``, + ``__index__`` and ``__coerce__`` +* Descriptor methods: ``__get__``, ``__set__`` and ``__delete__`` +* Pickling: ``__reduce__``, ``__reduce_ex__``, ``__getinitargs__``, + ``__getnewargs__``, ``__getstate__`` and ``__setstate__`` + + +The following methods are supported in Python 2 but don't exist in Python 3: + +* ``__unicode__``, ``__long__``, ``__oct__``, ``__hex__`` and ``__nonzero__`` +* ``__truediv__`` and ``__rtruediv__`` + +The following methods are supported in Python 3 but don't exist in Python 2: + +* ``__bool__`` and ``__next__`` + +The following methods exist but are *not* supported as they are either in use by +mock, can't be set dynamically, or can cause problems: + +* ``__getattr__``, ``__setattr__``, ``__init__`` and ``__new__`` +* ``__prepare__``, ``__instancecheck__``, ``__subclasscheck__``, ``__del__`` + + + +Magic Mock +========== + +There are two `MagicMock` variants: `MagicMock` and `NonCallableMagicMock`. + + +.. class:: MagicMock(*args, **kw) + + ``MagicMock`` is a subclass of :class:`Mock` with default implementations + of most of the magic methods. You can use ``MagicMock`` without having to + configure the magic methods yourself. + + The constructor parameters have the same meaning as for :class:`Mock`. + + If you use the `spec` or `spec_set` arguments then *only* magic methods + that exist in the spec will be created. + + +.. class:: NonCallableMagicMock(*args, **kw) + + A non-callable version of `MagicMock`. + + The constructor parameters have the same meaning as for + :class:`MagicMock`, with the exception of `return_value` and + `side_effect` which have no meaning on a non-callable mock. + +The magic methods are setup with `MagicMock` objects, so you can configure them +and use them in the usual way: + +.. doctest:: + + >>> mock = MagicMock() + >>> mock[3] = 'fish' + >>> mock.__setitem__.assert_called_with(3, 'fish') + >>> mock.__getitem__.return_value = 'result' + >>> mock[2] + 'result' + +By default many of the protocol methods are required to return objects of a +specific type. These methods are preconfigured with a default return value, so +that they can be used without you having to do anything if you aren't interested +in the return value. You can still *set* the return value manually if you want +to change the default. + +Methods and their defaults: + +* ``__lt__``: NotImplemented +* ``__gt__``: NotImplemented +* ``__le__``: NotImplemented +* ``__ge__``: NotImplemented +* ``__int__`` : 1 +* ``__contains__`` : False +* ``__len__`` : 1 +* ``__iter__`` : iter([]) +* ``__exit__`` : False +* ``__complex__`` : 1j +* ``__float__`` : 1.0 +* ``__bool__`` : True +* ``__nonzero__`` : True +* ``__oct__`` : '1' +* ``__hex__`` : '0x1' +* ``__long__`` : long(1) +* ``__index__`` : 1 +* ``__hash__`` : default hash for the mock +* ``__str__`` : default str for the mock +* ``__unicode__`` : default unicode for the mock +* ``__sizeof__``: default sizeof for the mock + +For example: + +.. doctest:: + + >>> mock = MagicMock() + >>> int(mock) + 1 + >>> len(mock) + 0 + >>> hex(mock) + '0x1' + >>> list(mock) + [] + >>> object() in mock + False + +The two equality method, `__eq__` and `__ne__`, are special (changed in +0.7.2). They do the default equality comparison on identity, using a side +effect, unless you change their return value to return something else: + +.. doctest:: + + >>> MagicMock() == 3 + False + >>> MagicMock() != 3 + True + >>> mock = MagicMock() + >>> mock.__eq__.return_value = True + >>> mock == 3 + True + +In `0.8` the `__iter__` also gained special handling implemented with a +side effect. The return value of `MagicMock.__iter__` can be any iterable +object and isn't required to be an iterator: + +.. doctest:: + + >>> mock = MagicMock() + >>> mock.__iter__.return_value = ['a', 'b', 'c'] + >>> list(mock) + ['a', 'b', 'c'] + >>> list(mock) + ['a', 'b', 'c'] + +If the return value *is* an iterator, then iterating over it once will consume +it and subsequent iterations will result in an empty list: + +.. doctest:: + + >>> mock.__iter__.return_value = iter(['a', 'b', 'c']) + >>> list(mock) + ['a', 'b', 'c'] + >>> list(mock) + [] + +``MagicMock`` has all of the supported magic methods configured except for some +of the obscure and obsolete ones. You can still set these up if you want. + +Magic methods that are supported but not setup by default in ``MagicMock`` are: + +* ``__cmp__`` +* ``__getslice__`` and ``__setslice__`` +* ``__coerce__`` +* ``__subclasses__`` +* ``__dir__`` +* ``__format__`` +* ``__get__``, ``__set__`` and ``__delete__`` +* ``__reversed__`` and ``__missing__`` +* ``__reduce__``, ``__reduce_ex__``, ``__getinitargs__``, ``__getnewargs__``, + ``__getstate__`` and ``__setstate__`` +* ``__getformat__`` and ``__setformat__`` + + + +------------ + +.. [#] Magic methods *should* be looked up on the class rather than the + instance. Different versions of Python are inconsistent about applying this + rule. The supported protocol methods should work with all supported versions + of Python. +.. [#] The function is basically hooked up to the class, but each ``Mock`` + instance is kept isolated from the others. diff --git a/python/mock-1.0.0/html/_sources/mock.txt b/python/mock-1.0.0/html/_sources/mock.txt new file mode 100644 index 00000000000..58712b21a67 --- /dev/null +++ b/python/mock-1.0.0/html/_sources/mock.txt @@ -0,0 +1,842 @@ +The Mock Class +============== + +.. currentmodule:: mock + +.. testsetup:: + + class SomeClass: + pass + + +`Mock` is a flexible mock object intended to replace the use of stubs and +test doubles throughout your code. Mocks are callable and create attributes as +new mocks when you access them [#]_. Accessing the same attribute will always +return the same mock. Mocks record how you use them, allowing you to make +assertions about what your code has done to them. + +:class:`MagicMock` is a subclass of `Mock` with all the magic methods +pre-created and ready to use. There are also non-callable variants, useful +when you are mocking out objects that aren't callable: +:class:`NonCallableMock` and :class:`NonCallableMagicMock` + +The :func:`patch` decorators makes it easy to temporarily replace classes +in a particular module with a `Mock` object. By default `patch` will create +a `MagicMock` for you. You can specify an alternative class of `Mock` using +the `new_callable` argument to `patch`. + + +.. index:: side_effect +.. index:: return_value +.. index:: wraps +.. index:: name +.. index:: spec + +.. class:: Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, **kwargs) + + Create a new `Mock` object. `Mock` takes several optional arguments + that specify the behaviour of the Mock object: + + * `spec`: This can be either a list of strings or an existing object (a + class or instance) that acts as the specification for the mock object. If + you pass in an object then a list of strings is formed by calling dir on + the object (excluding unsupported magic attributes and methods). + Accessing any attribute not in this list will raise an `AttributeError`. + + If `spec` is an object (rather than a list of strings) then + :attr:`__class__` returns the class of the spec object. This allows mocks + to pass `isinstance` tests. + + * `spec_set`: A stricter variant of `spec`. If used, attempting to *set* + or get an attribute on the mock that isn't on the object passed as + `spec_set` will raise an `AttributeError`. + + * `side_effect`: A function to be called whenever the Mock is called. See + the :attr:`~Mock.side_effect` attribute. Useful for raising exceptions or + dynamically changing return values. The function is called with the same + arguments as the mock, and unless it returns :data:`DEFAULT`, the return + value of this function is used as the return value. + + Alternatively `side_effect` can be an exception class or instance. In + this case the exception will be raised when the mock is called. + + If `side_effect` is an iterable then each call to the mock will return + the next value from the iterable. If any of the members of the iterable + are exceptions they will be raised instead of returned. + + A `side_effect` can be cleared by setting it to `None`. + + * `return_value`: The value returned when the mock is called. By default + this is a new Mock (created on first access). See the + :attr:`return_value` attribute. + + * `wraps`: Item for the mock object to wrap. If `wraps` is not None then + calling the Mock will pass the call through to the wrapped object + (returning the real result and ignoring `return_value`). Attribute access + on the mock will return a Mock object that wraps the corresponding + attribute of the wrapped object (so attempting to access an attribute + that doesn't exist will raise an `AttributeError`). + + If the mock has an explicit `return_value` set then calls are not passed + to the wrapped object and the `return_value` is returned instead. + + * `name`: If the mock has a name then it will be used in the repr of the + mock. This can be useful for debugging. The name is propagated to child + mocks. + + Mocks can also be called with arbitrary keyword arguments. These will be + used to set attributes on the mock after it is created. See the + :meth:`configure_mock` method for details. + + + .. method:: assert_called_with(*args, **kwargs) + + This method is a convenient way of asserting that calls are made in a + particular way: + + .. doctest:: + + >>> mock = Mock() + >>> mock.method(1, 2, 3, test='wow') + + >>> mock.method.assert_called_with(1, 2, 3, test='wow') + + + .. method:: assert_called_once_with(*args, **kwargs) + + Assert that the mock was called exactly once and with the specified + arguments. + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock('foo', bar='baz') + >>> mock.assert_called_once_with('foo', bar='baz') + >>> mock('foo', bar='baz') + >>> mock.assert_called_once_with('foo', bar='baz') + Traceback (most recent call last): + ... + AssertionError: Expected to be called once. Called 2 times. + + + .. method:: assert_any_call(*args, **kwargs) + + assert the mock has been called with the specified arguments. + + The assert passes if the mock has *ever* been called, unlike + :meth:`assert_called_with` and :meth:`assert_called_once_with` that + only pass if the call is the most recent one. + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock(1, 2, arg='thing') + >>> mock('some', 'thing', 'else') + >>> mock.assert_any_call(1, 2, arg='thing') + + + .. method:: assert_has_calls(calls, any_order=False) + + assert the mock has been called with the specified calls. + The `mock_calls` list is checked for the calls. + + If `any_order` is False (the default) then the calls must be + sequential. There can be extra calls before or after the + specified calls. + + If `any_order` is True then the calls can be in any order, but + they must all appear in :attr:`mock_calls`. + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock(1) + >>> mock(2) + >>> mock(3) + >>> mock(4) + >>> calls = [call(2), call(3)] + >>> mock.assert_has_calls(calls) + >>> calls = [call(4), call(2), call(3)] + >>> mock.assert_has_calls(calls, any_order=True) + + + .. method:: reset_mock() + + The reset_mock method resets all the call attributes on a mock object: + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock('hello') + >>> mock.called + True + >>> mock.reset_mock() + >>> mock.called + False + + This can be useful where you want to make a series of assertions that + reuse the same object. Note that `reset_mock` *doesn't* clear the + return value, :attr:`side_effect` or any child attributes you have + set using normal assignment. Child mocks and the return value mock + (if any) are reset as well. + + + .. method:: mock_add_spec(spec, spec_set=False) + + Add a spec to a mock. `spec` can either be an object or a + list of strings. Only attributes on the `spec` can be fetched as + attributes from the mock. + + If `spec_set` is `True` then only attributes on the spec can be set. + + + .. method:: attach_mock(mock, attribute) + + Attach a mock as an attribute of this one, replacing its name and + parent. Calls to the attached mock will be recorded in the + :attr:`method_calls` and :attr:`mock_calls` attributes of this one. + + + .. method:: configure_mock(**kwargs) + + Set attributes on the mock through keyword arguments. + + Attributes plus return values and side effects can be set on child + mocks using standard dot notation and unpacking a dictionary in the + method call: + + .. doctest:: + + >>> mock = Mock() + >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} + >>> mock.configure_mock(**attrs) + >>> mock.method() + 3 + >>> mock.other() + Traceback (most recent call last): + ... + KeyError + + The same thing can be achieved in the constructor call to mocks: + + .. doctest:: + + >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} + >>> mock = Mock(some_attribute='eggs', **attrs) + >>> mock.some_attribute + 'eggs' + >>> mock.method() + 3 + >>> mock.other() + Traceback (most recent call last): + ... + KeyError + + `configure_mock` exists to make it easier to do configuration + after the mock has been created. + + + .. method:: __dir__() + + `Mock` objects limit the results of `dir(some_mock)` to useful results. + For mocks with a `spec` this includes all the permitted attributes + for the mock. + + See :data:`FILTER_DIR` for what this filtering does, and how to + switch it off. + + + .. method:: _get_child_mock(**kw) + + Create the child mocks for attributes and return value. + By default child mocks will be the same type as the parent. + Subclasses of Mock may want to override this to customize the way + child mocks are made. + + For non-callable mocks the callable variant will be used (rather than + any custom subclass). + + + .. attribute:: called + + A boolean representing whether or not the mock object has been called: + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock.called + False + >>> mock() + >>> mock.called + True + + .. attribute:: call_count + + An integer telling you how many times the mock object has been called: + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock.call_count + 0 + >>> mock() + >>> mock() + >>> mock.call_count + 2 + + + .. attribute:: return_value + + Set this to configure the value returned by calling the mock: + + .. doctest:: + + >>> mock = Mock() + >>> mock.return_value = 'fish' + >>> mock() + 'fish' + + The default return value is a mock object and you can configure it in + the normal way: + + .. doctest:: + + >>> mock = Mock() + >>> mock.return_value.attribute = sentinel.Attribute + >>> mock.return_value() + + >>> mock.return_value.assert_called_with() + + `return_value` can also be set in the constructor: + + .. doctest:: + + >>> mock = Mock(return_value=3) + >>> mock.return_value + 3 + >>> mock() + 3 + + + .. attribute:: side_effect + + This can either be a function to be called when the mock is called, + or an exception (class or instance) to be raised. + + If you pass in a function it will be called with same arguments as the + mock and unless the function returns the :data:`DEFAULT` singleton the + call to the mock will then return whatever the function returns. If the + function returns :data:`DEFAULT` then the mock will return its normal + value (from the :attr:`return_value`. + + An example of a mock that raises an exception (to test exception + handling of an API): + + .. doctest:: + + >>> mock = Mock() + >>> mock.side_effect = Exception('Boom!') + >>> mock() + Traceback (most recent call last): + ... + Exception: Boom! + + Using `side_effect` to return a sequence of values: + + .. doctest:: + + >>> mock = Mock() + >>> mock.side_effect = [3, 2, 1] + >>> mock(), mock(), mock() + (3, 2, 1) + + The `side_effect` function is called with the same arguments as the + mock (so it is wise for it to take arbitrary args and keyword + arguments) and whatever it returns is used as the return value for + the call. The exception is if `side_effect` returns :data:`DEFAULT`, + in which case the normal :attr:`return_value` is used. + + .. doctest:: + + >>> mock = Mock(return_value=3) + >>> def side_effect(*args, **kwargs): + ... return DEFAULT + ... + >>> mock.side_effect = side_effect + >>> mock() + 3 + + `side_effect` can be set in the constructor. Here's an example that + adds one to the value the mock is called with and returns it: + + .. doctest:: + + >>> side_effect = lambda value: value + 1 + >>> mock = Mock(side_effect=side_effect) + >>> mock(3) + 4 + >>> mock(-8) + -7 + + Setting `side_effect` to `None` clears it: + + .. doctest:: + + >>> from mock import Mock + >>> m = Mock(side_effect=KeyError, return_value=3) + >>> m() + Traceback (most recent call last): + ... + KeyError + >>> m.side_effect = None + >>> m() + 3 + + + .. attribute:: call_args + + This is either `None` (if the mock hasn't been called), or the + arguments that the mock was last called with. This will be in the + form of a tuple: the first member is any ordered arguments the mock + was called with (or an empty tuple) and the second member is any + keyword arguments (or an empty dictionary). + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> print mock.call_args + None + >>> mock() + >>> mock.call_args + call() + >>> mock.call_args == () + True + >>> mock(3, 4) + >>> mock.call_args + call(3, 4) + >>> mock.call_args == ((3, 4),) + True + >>> mock(3, 4, 5, key='fish', next='w00t!') + >>> mock.call_args + call(3, 4, 5, key='fish', next='w00t!') + + `call_args`, along with members of the lists :attr:`call_args_list`, + :attr:`method_calls` and :attr:`mock_calls` are :data:`call` objects. + These are tuples, so they can be unpacked to get at the individual + arguments and make more complex assertions. See + :ref:`calls as tuples `. + + + .. attribute:: call_args_list + + This is a list of all the calls made to the mock object in sequence + (so the length of the list is the number of times it has been + called). Before any calls have been made it is an empty list. The + :data:`call` object can be used for conveniently constructing lists of + calls to compare with `call_args_list`. + + .. doctest:: + + >>> mock = Mock(return_value=None) + >>> mock() + >>> mock(3, 4) + >>> mock(key='fish', next='w00t!') + >>> mock.call_args_list + [call(), call(3, 4), call(key='fish', next='w00t!')] + >>> expected = [(), ((3, 4),), ({'key': 'fish', 'next': 'w00t!'},)] + >>> mock.call_args_list == expected + True + + Members of `call_args_list` are :data:`call` objects. These can be + unpacked as tuples to get at the individual arguments. See + :ref:`calls as tuples `. + + + .. attribute:: method_calls + + As well as tracking calls to themselves, mocks also track calls to + methods and attributes, and *their* methods and attributes: + + .. doctest:: + + >>> mock = Mock() + >>> mock.method() + + >>> mock.property.method.attribute() + + >>> mock.method_calls + [call.method(), call.property.method.attribute()] + + Members of `method_calls` are :data:`call` objects. These can be + unpacked as tuples to get at the individual arguments. See + :ref:`calls as tuples `. + + + .. attribute:: mock_calls + + `mock_calls` records *all* calls to the mock object, its methods, magic + methods *and* return value mocks. + + .. doctest:: + + >>> mock = MagicMock() + >>> result = mock(1, 2, 3) + >>> mock.first(a=3) + + >>> mock.second() + + >>> int(mock) + 1 + >>> result(1) + + >>> expected = [call(1, 2, 3), call.first(a=3), call.second(), + ... call.__int__(), call()(1)] + >>> mock.mock_calls == expected + True + + Members of `mock_calls` are :data:`call` objects. These can be + unpacked as tuples to get at the individual arguments. See + :ref:`calls as tuples `. + + + .. attribute:: __class__ + + Normally the `__class__` attribute of an object will return its type. + For a mock object with a `spec` `__class__` returns the spec class + instead. This allows mock objects to pass `isinstance` tests for the + object they are replacing / masquerading as: + + .. doctest:: + + >>> mock = Mock(spec=3) + >>> isinstance(mock, int) + True + + `__class__` is assignable to, this allows a mock to pass an + `isinstance` check without forcing you to use a spec: + + .. doctest:: + + >>> mock = Mock() + >>> mock.__class__ = dict + >>> isinstance(mock, dict) + True + +.. class:: NonCallableMock(spec=None, wraps=None, name=None, spec_set=None, **kwargs) + + A non-callable version of `Mock`. The constructor parameters have the same + meaning of `Mock`, with the exception of `return_value` and `side_effect` + which have no meaning on a non-callable mock. + +Mock objects that use a class or an instance as a `spec` or `spec_set` are able +to pass `isintance` tests: + +.. doctest:: + + >>> mock = Mock(spec=SomeClass) + >>> isinstance(mock, SomeClass) + True + >>> mock = Mock(spec_set=SomeClass()) + >>> isinstance(mock, SomeClass) + True + +The `Mock` classes have support for mocking magic methods. See :ref:`magic +methods ` for the full details. + +The mock classes and the :func:`patch` decorators all take arbitrary keyword +arguments for configuration. For the `patch` decorators the keywords are +passed to the constructor of the mock being created. The keyword arguments +are for configuring attributes of the mock: + +.. doctest:: + + >>> m = MagicMock(attribute=3, other='fish') + >>> m.attribute + 3 + >>> m.other + 'fish' + +The return value and side effect of child mocks can be set in the same way, +using dotted notation. As you can't use dotted names directly in a call you +have to create a dictionary and unpack it using `**`: + +.. doctest:: + + >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} + >>> mock = Mock(some_attribute='eggs', **attrs) + >>> mock.some_attribute + 'eggs' + >>> mock.method() + 3 + >>> mock.other() + Traceback (most recent call last): + ... + KeyError + + +.. class:: PropertyMock(*args, **kwargs) + + A mock intended to be used as a property, or other descriptor, on a class. + `PropertyMock` provides `__get__` and `__set__` methods so you can specify + a return value when it is fetched. + + Fetching a `PropertyMock` instance from an object calls the mock, with + no args. Setting it calls the mock with the value being set. + + .. doctest:: + + >>> class Foo(object): + ... @property + ... def foo(self): + ... return 'something' + ... @foo.setter + ... def foo(self, value): + ... pass + ... + >>> with patch('__main__.Foo.foo', new_callable=PropertyMock) as mock_foo: + ... mock_foo.return_value = 'mockity-mock' + ... this_foo = Foo() + ... print this_foo.foo + ... this_foo.foo = 6 + ... + mockity-mock + >>> mock_foo.mock_calls + [call(), call(6)] + +Because of the way mock attributes are stored you can't directly attach a +`PropertyMock` to a mock object. Instead you can attach it to the mock type +object: + +.. doctest:: + + >>> m = MagicMock() + >>> p = PropertyMock(return_value=3) + >>> type(m).foo = p + >>> m.foo + 3 + >>> p.assert_called_once_with() + + +.. index:: __call__ +.. index:: calling + +Calling +======= + +Mock objects are callable. The call will return the value set as the +:attr:`~Mock.return_value` attribute. The default return value is a new Mock +object; it is created the first time the return value is accessed (either +explicitly or by calling the Mock) - but it is stored and the same one +returned each time. + +Calls made to the object will be recorded in the attributes +like :attr:`~Mock.call_args` and :attr:`~Mock.call_args_list`. + +If :attr:`~Mock.side_effect` is set then it will be called after the call has +been recorded, so if `side_effect` raises an exception the call is still +recorded. + +The simplest way to make a mock raise an exception when called is to make +:attr:`~Mock.side_effect` an exception class or instance: + +.. doctest:: + + >>> m = MagicMock(side_effect=IndexError) + >>> m(1, 2, 3) + Traceback (most recent call last): + ... + IndexError + >>> m.mock_calls + [call(1, 2, 3)] + >>> m.side_effect = KeyError('Bang!') + >>> m('two', 'three', 'four') + Traceback (most recent call last): + ... + KeyError: 'Bang!' + >>> m.mock_calls + [call(1, 2, 3), call('two', 'three', 'four')] + +If `side_effect` is a function then whatever that function returns is what +calls to the mock return. The `side_effect` function is called with the +same arguments as the mock. This allows you to vary the return value of the +call dynamically, based on the input: + +.. doctest:: + + >>> def side_effect(value): + ... return value + 1 + ... + >>> m = MagicMock(side_effect=side_effect) + >>> m(1) + 2 + >>> m(2) + 3 + >>> m.mock_calls + [call(1), call(2)] + +If you want the mock to still return the default return value (a new mock), or +any set return value, then there are two ways of doing this. Either return +`mock.return_value` from inside `side_effect`, or return :data:`DEFAULT`: + +.. doctest:: + + >>> m = MagicMock() + >>> def side_effect(*args, **kwargs): + ... return m.return_value + ... + >>> m.side_effect = side_effect + >>> m.return_value = 3 + >>> m() + 3 + >>> def side_effect(*args, **kwargs): + ... return DEFAULT + ... + >>> m.side_effect = side_effect + >>> m() + 3 + +To remove a `side_effect`, and return to the default behaviour, set the +`side_effect` to `None`: + +.. doctest:: + + >>> m = MagicMock(return_value=6) + >>> def side_effect(*args, **kwargs): + ... return 3 + ... + >>> m.side_effect = side_effect + >>> m() + 3 + >>> m.side_effect = None + >>> m() + 6 + +The `side_effect` can also be any iterable object. Repeated calls to the mock +will return values from the iterable (until the iterable is exhausted and +a `StopIteration` is raised): + +.. doctest:: + + >>> m = MagicMock(side_effect=[1, 2, 3]) + >>> m() + 1 + >>> m() + 2 + >>> m() + 3 + >>> m() + Traceback (most recent call last): + ... + StopIteration + +If any members of the iterable are exceptions they will be raised instead of +returned: + +.. doctest:: + + >>> iterable = (33, ValueError, 66) + >>> m = MagicMock(side_effect=iterable) + >>> m() + 33 + >>> m() + Traceback (most recent call last): + ... + ValueError + >>> m() + 66 + + +.. _deleting-attributes: + +Deleting Attributes +=================== + +Mock objects create attributes on demand. This allows them to pretend to be +objects of any type. + +You may want a mock object to return `False` to a `hasattr` call, or raise an +`AttributeError` when an attribute is fetched. You can do this by providing +an object as a `spec` for a mock, but that isn't always convenient. + +You "block" attributes by deleting them. Once deleted, accessing an attribute +will raise an `AttributeError`. + +.. doctest:: + + >>> mock = MagicMock() + >>> hasattr(mock, 'm') + True + >>> del mock.m + >>> hasattr(mock, 'm') + False + >>> del mock.f + >>> mock.f + Traceback (most recent call last): + ... + AttributeError: f + + +Attaching Mocks as Attributes +============================= + +When you attach a mock as an attribute of another mock (or as the return +value) it becomes a "child" of that mock. Calls to the child are recorded in +the :attr:`~Mock.method_calls` and :attr:`~Mock.mock_calls` attributes of the +parent. This is useful for configuring child mocks and then attaching them to +the parent, or for attaching mocks to a parent that records all calls to the +children and allows you to make assertions about the order of calls between +mocks: + +.. doctest:: + + >>> parent = MagicMock() + >>> child1 = MagicMock(return_value=None) + >>> child2 = MagicMock(return_value=None) + >>> parent.child1 = child1 + >>> parent.child2 = child2 + >>> child1(1) + >>> child2(2) + >>> parent.mock_calls + [call.child1(1), call.child2(2)] + +The exception to this is if the mock has a name. This allows you to prevent +the "parenting" if for some reason you don't want it to happen. + +.. doctest:: + + >>> mock = MagicMock() + >>> not_a_child = MagicMock(name='not-a-child') + >>> mock.attribute = not_a_child + >>> mock.attribute() + + >>> mock.mock_calls + [] + +Mocks created for you by :func:`patch` are automatically given names. To +attach mocks that have names to a parent you use the :meth:`~Mock.attach_mock` +method: + +.. doctest:: + + >>> thing1 = object() + >>> thing2 = object() + >>> parent = MagicMock() + >>> with patch('__main__.thing1', return_value=None) as child1: + ... with patch('__main__.thing2', return_value=None) as child2: + ... parent.attach_mock(child1, 'child1') + ... parent.attach_mock(child2, 'child2') + ... child1('one') + ... child2('two') + ... + >>> parent.mock_calls + [call.child1('one'), call.child2('two')] + + +----- + +.. [#] The only exceptions are magic methods and attributes (those that have + leading and trailing double underscores). Mock doesn't create these but + instead of raises an ``AttributeError``. This is because the interpreter + will often implicitly request these methods, and gets *very* confused to + get a new Mock object when it expects a magic method. If you need magic + method support see :ref:`magic methods `. diff --git a/python/mock-1.0.0/html/_sources/mocksignature.txt b/python/mock-1.0.0/html/_sources/mocksignature.txt new file mode 100644 index 00000000000..dbb5019fbbf --- /dev/null +++ b/python/mock-1.0.0/html/_sources/mocksignature.txt @@ -0,0 +1,262 @@ +mocksignature +============= + +.. currentmodule:: mock + +.. note:: + + :ref:`auto-speccing`, added in mock 0.8, is a more advanced version of + `mocksignature` and can be used for many of the same use cases. + +A problem with using mock objects to replace real objects in your tests is that +:class:`Mock` can be *too* flexible. Your code can treat the mock objects in +any way and you have to manually check that they were called correctly. If your +code calls functions or methods with the wrong number of arguments then mocks +don't complain. + +The solution to this is `mocksignature`, which creates functions with the +same signature as the original, but delegating to a mock. You can interrogate +the mock in the usual way to check it has been called with the *right* +arguments, but if it is called with the wrong number of arguments it will +raise a `TypeError` in the same way your production code would. + +Another advantage is that your mocked objects are real functions, which can +be useful when your code uses +`inspect `_ or depends on +functions being function objects. + +.. function:: mocksignature(func, mock=None, skipfirst=False) + + Create a new function with the same signature as `func` that delegates + to `mock`. If `skipfirst` is True the first argument is skipped, useful + for methods where `self` needs to be omitted from the new function. + + If you don't pass in a `mock` then one will be created for you. + + Functions returned by `mocksignature` have many of the same attributes + and assert methods as a mock object. + + The mock is set as the `mock` attribute of the returned function for easy + access. + + `mocksignature` can also be used with classes. It copies the signature of + the `__init__` method. + + When used with callable objects (instances) it copies the signature of the + `__call__` method. + +`mocksignature` will work out if it is mocking the signature of a method on +an instance or a method on a class and do the "right thing" with the `self` +argument in both cases. + +Because of a limitation in the way that arguments are collected by functions +created by `mocksignature` they are *always* passed as positional arguments +(including defaults) and not keyword arguments. + + +mocksignature api +----------------- + +Although the objects returned by `mocksignature` api are real function objects, +they have much of the same api as the :class:`Mock` class. This includes the +assert methods: + +.. doctest:: + + >>> def func(a, b, c): + ... pass + ... + >>> func2 = mocksignature(func) + >>> func2.called + False + >>> func2.return_value = 3 + >>> func2(1, 2, 3) + 3 + >>> func2.called + True + >>> func2.assert_called_once_with(1, 2, 3) + >>> func2.assert_called_with(1, 2, 4) + Traceback (most recent call last): + ... + AssertionError: Expected call: mock(1, 2, 4) + Actual call: mock(1, 2, 3) + >>> func2.call_count + 1 + >>> func2.side_effect = IndexError + >>> func2(4, 5, 6) + Traceback (most recent call last): + ... + IndexError + +The mock object that is being delegated to is available as the `mock` attribute +of the function created by `mocksignature`. + +.. doctest:: + + >>> func2.mock.mock_calls + [call(1, 2, 3), call(4, 5, 6)] + +The methods and attributes available on functions returned by `mocksignature` +are: + + :meth:`~Mock.assert_any_call`, :meth:`~Mock.assert_called_once_with`, + :meth:`~Mock.assert_called_with`, :meth:`~Mock.assert_has_calls`, + :attr:`~Mock.call_args`, :attr:`~Mock.call_args_list`, + :attr:`~Mock.call_count`, :attr:`~Mock.called`, + :attr:`~Mock.method_calls`, `mock`, :attr:`~Mock.mock_calls`, + :meth:`~Mock.reset_mock`, :attr:`~Mock.return_value`, and + :attr:`~Mock.side_effect`. + + +Example use +----------- + +Basic use +~~~~~~~~~ + +.. doctest:: + + >>> def function(a, b, c=None): + ... pass + ... + >>> mock = Mock() + >>> function = mocksignature(function, mock) + >>> function() + Traceback (most recent call last): + ... + TypeError: () takes at least 2 arguments (0 given) + >>> function.return_value = 'some value' + >>> function(1, 2, 'foo') + 'some value' + >>> function.assert_called_with(1, 2, 'foo') + + +Keyword arguments +~~~~~~~~~~~~~~~~~ + +Note that arguments to functions created by `mocksignature` are always passed +in to the underlying mock by position even when called with keywords: + +.. doctest:: + + >>> def function(a, b, c=None): + ... pass + ... + >>> function = mocksignature(function) + >>> function.return_value = None + >>> function(1, 2) + >>> function.assert_called_with(1, 2, None) + + +Mocking methods and self +~~~~~~~~~~~~~~~~~~~~~~~~ + +When you use `mocksignature` to replace a method on a class then `self` +will be included in the method signature - and you will need to include +the instance when you do your asserts. + +As a curious factor of the way Python (2) wraps methods fetched from a class, +we can *get* the `return_value` from a function set on a class, but we can't +set it. We have to do this through the exposed `mock` attribute instead: + +.. doctest:: + + >>> class SomeClass(object): + ... def method(self, a, b, c=None): + ... pass + ... + >>> SomeClass.method = mocksignature(SomeClass.method) + >>> SomeClass.method.mock.return_value = None + >>> instance = SomeClass() + >>> instance.method() + Traceback (most recent call last): + ... + TypeError: () takes at least 4 arguments (1 given) + >>> instance.method(1, 2, 3) + >>> instance.method.assert_called_with(instance, 1, 2, 3) + +When you use `mocksignature` on instance methods `self` isn't included (and we +can set the `return_value` etc directly): + +.. doctest:: + + >>> class SomeClass(object): + ... def method(self, a, b, c=None): + ... pass + ... + >>> instance = SomeClass() + >>> instance.method = mocksignature(instance.method) + >>> instance.method.return_value = None + >>> instance.method(1, 2, 3) + >>> instance.method.assert_called_with(1, 2, 3) + + +mocksignature with classes +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When used with a class `mocksignature` copies the signature of the `__init__` +method. + +.. doctest:: + + >>> class Something(object): + ... def __init__(self, foo, bar): + ... pass + ... + >>> MockSomething = mocksignature(Something) + >>> instance = MockSomething(10, 9) + >>> assert instance is MockSomething.return_value + >>> MockSomething.assert_called_with(10, 9) + >>> MockSomething() + Traceback (most recent call last): + ... + TypeError: () takes at least 2 arguments (0 given) + +Because the object returned by `mocksignature` is a function rather than a +`Mock` you lose the other capabilities of `Mock`, like dynamic attribute +creation. + + +mocksignature with callable objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When used with a callable object `mocksignature` copies the signature of the +`__call__` method. + +.. doctest:: + + >>> class Something(object): + ... def __call__(self, spam, eggs): + ... pass + ... + >>> something = Something() + >>> mock_something = mocksignature(something) + >>> result = mock_something(10, 9) + >>> mock_something.assert_called_with(10, 9) + >>> mock_something() + Traceback (most recent call last): + ... + TypeError: () takes at least 2 arguments (0 given) + + +mocksignature argument to patch +------------------------------- + +`mocksignature` is available as a keyword argument to :func:`patch` or +:func:`patch.object`. It can be used with functions / methods / classes and +callable objects. + +.. doctest:: + + >>> class SomeClass(object): + ... def method(self, a, b, c=None): + ... pass + ... + >>> @patch.object(SomeClass, 'method', mocksignature=True) + ... def test(mock_method): + ... instance = SomeClass() + ... mock_method.return_value = None + ... instance.method(1, 2) + ... mock_method.assert_called_with(instance, 1, 2, None) + ... + >>> test() diff --git a/python/mock-1.0.0/html/_sources/patch.txt b/python/mock-1.0.0/html/_sources/patch.txt new file mode 100644 index 00000000000..3d56264fbbf --- /dev/null +++ b/python/mock-1.0.0/html/_sources/patch.txt @@ -0,0 +1,636 @@ +================== + Patch Decorators +================== + + +.. currentmodule:: mock + +.. testsetup:: + + class SomeClass(object): + static_method = None + class_method = None + attribute = None + + sys.modules['package'] = package = Mock(name='package') + sys.modules['package.module'] = package.module + + class TestCase(unittest2.TestCase): + def run(self): + result = unittest2.TestResult() + super(unittest2.TestCase, self).run(result) + assert result.wasSuccessful() + +.. testcleanup:: + + patch.TEST_PREFIX = 'test' + + +The patch decorators are used for patching objects only within the scope of +the function they decorate. They automatically handle the unpatching for you, +even if exceptions are raised. All of these functions can also be used in with +statements or as class decorators. + + +patch +===== + +.. note:: + + `patch` is straightforward to use. The key is to do the patching in the + right namespace. See the section `where to patch`_. + +.. function:: patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs) + + `patch` acts as a function decorator, class decorator or a context + manager. Inside the body of the function or with statement, the `target` + is patched with a `new` object. When the function/with statement exits + the patch is undone. + + If `new` is omitted, then the target is replaced with a + :class:`MagicMock`. If `patch` is used as a decorator and `new` is + omitted, the created mock is passed in as an extra argument to the + decorated function. If `patch` is used as a context manager the created + mock is returned by the context manager. + + `target` should be a string in the form `'package.module.ClassName'`. The + `target` is imported and the specified object replaced with the `new` + object, so the `target` must be importable from the environment you are + calling `patch` from. The target is imported when the decorated function + is executed, not at decoration time. + + The `spec` and `spec_set` keyword arguments are passed to the `MagicMock` + if patch is creating one for you. + + In addition you can pass `spec=True` or `spec_set=True`, which causes + patch to pass in the object being mocked as the spec/spec_set object. + + `new_callable` allows you to specify a different class, or callable object, + that will be called to create the `new` object. By default `MagicMock` is + used. + + A more powerful form of `spec` is `autospec`. If you set `autospec=True` + then the mock with be created with a spec from the object being replaced. + All attributes of the mock will also have the spec of the corresponding + attribute of the object being replaced. Methods and functions being mocked + will have their arguments checked and will raise a `TypeError` if they are + called with the wrong signature. For mocks + replacing a class, their return value (the 'instance') will have the same + spec as the class. See the :func:`create_autospec` function and + :ref:`auto-speccing`. + + Instead of `autospec=True` you can pass `autospec=some_object` to use an + arbitrary object as the spec instead of the one being replaced. + + By default `patch` will fail to replace attributes that don't exist. If + you pass in `create=True`, and the attribute doesn't exist, patch will + create the attribute for you when the patched function is called, and + delete it again afterwards. This is useful for writing tests against + attributes that your production code creates at runtime. It is off by by + default because it can be dangerous. With it switched on you can write + passing tests against APIs that don't actually exist! + + Patch can be used as a `TestCase` class decorator. It works by + decorating each test method in the class. This reduces the boilerplate + code when your test methods share a common patchings set. `patch` finds + tests by looking for method names that start with `patch.TEST_PREFIX`. + By default this is `test`, which matches the way `unittest` finds tests. + You can specify an alternative prefix by setting `patch.TEST_PREFIX`. + + Patch can be used as a context manager, with the with statement. Here the + patching applies to the indented block after the with statement. If you + use "as" then the patched object will be bound to the name after the + "as"; very useful if `patch` is creating a mock object for you. + + `patch` takes arbitrary keyword arguments. These will be passed to + the `Mock` (or `new_callable`) on construction. + + `patch.dict(...)`, `patch.multiple(...)` and `patch.object(...)` are + available for alternate use-cases. + +`patch` as function decorator, creating the mock for you and passing it into +the decorated function: + +.. doctest:: + + >>> @patch('__main__.SomeClass') + ... def function(normal_argument, mock_class): + ... print mock_class is SomeClass + ... + >>> function(None) + True + + +Patching a class replaces the class with a `MagicMock` *instance*. If the +class is instantiated in the code under test then it will be the +:attr:`~Mock.return_value` of the mock that will be used. + +If the class is instantiated multiple times you could use +:attr:`~Mock.side_effect` to return a new mock each time. Alternatively you +can set the `return_value` to be anything you want. + +To configure return values on methods of *instances* on the patched class +you must do this on the `return_value`. For example: + +.. doctest:: + + >>> class Class(object): + ... def method(self): + ... pass + ... + >>> with patch('__main__.Class') as MockClass: + ... instance = MockClass.return_value + ... instance.method.return_value = 'foo' + ... assert Class() is instance + ... assert Class().method() == 'foo' + ... + +If you use `spec` or `spec_set` and `patch` is replacing a *class*, then the +return value of the created mock will have the same spec. + +.. doctest:: + + >>> Original = Class + >>> patcher = patch('__main__.Class', spec=True) + >>> MockClass = patcher.start() + >>> instance = MockClass() + >>> assert isinstance(instance, Original) + >>> patcher.stop() + +The `new_callable` argument is useful where you want to use an alternative +class to the default :class:`MagicMock` for the created mock. For example, if +you wanted a :class:`NonCallableMock` to be used: + +.. doctest:: + + >>> thing = object() + >>> with patch('__main__.thing', new_callable=NonCallableMock) as mock_thing: + ... assert thing is mock_thing + ... thing() + ... + Traceback (most recent call last): + ... + TypeError: 'NonCallableMock' object is not callable + +Another use case might be to replace an object with a `StringIO` instance: + +.. doctest:: + + >>> from StringIO import StringIO + >>> def foo(): + ... print 'Something' + ... + >>> @patch('sys.stdout', new_callable=StringIO) + ... def test(mock_stdout): + ... foo() + ... assert mock_stdout.getvalue() == 'Something\n' + ... + >>> test() + +When `patch` is creating a mock for you, it is common that the first thing +you need to do is to configure the mock. Some of that configuration can be done +in the call to patch. Any arbitrary keywords you pass into the call will be +used to set attributes on the created mock: + +.. doctest:: + + >>> patcher = patch('__main__.thing', first='one', second='two') + >>> mock_thing = patcher.start() + >>> mock_thing.first + 'one' + >>> mock_thing.second + 'two' + +As well as attributes on the created mock attributes, like the +:attr:`~Mock.return_value` and :attr:`~Mock.side_effect`, of child mocks can +also be configured. These aren't syntactically valid to pass in directly as +keyword arguments, but a dictionary with these as keys can still be expanded +into a `patch` call using `**`: + +.. doctest:: + + >>> config = {'method.return_value': 3, 'other.side_effect': KeyError} + >>> patcher = patch('__main__.thing', **config) + >>> mock_thing = patcher.start() + >>> mock_thing.method() + 3 + >>> mock_thing.other() + Traceback (most recent call last): + ... + KeyError + + +patch.object +============ + +.. function:: patch.object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs) + + patch the named member (`attribute`) on an object (`target`) with a mock + object. + + `patch.object` can be used as a decorator, class decorator or a context + manager. Arguments `new`, `spec`, `create`, `spec_set`, `autospec` and + `new_callable` have the same meaning as for `patch`. Like `patch`, + `patch.object` takes arbitrary keyword arguments for configuring the mock + object it creates. + + When used as a class decorator `patch.object` honours `patch.TEST_PREFIX` + for choosing which methods to wrap. + +You can either call `patch.object` with three arguments or two arguments. The +three argument form takes the object to be patched, the attribute name and the +object to replace the attribute with. + +When calling with the two argument form you omit the replacement object, and a +mock is created for you and passed in as an extra argument to the decorated +function: + +.. doctest:: + + >>> @patch.object(SomeClass, 'class_method') + ... def test(mock_method): + ... SomeClass.class_method(3) + ... mock_method.assert_called_with(3) + ... + >>> test() + +`spec`, `create` and the other arguments to `patch.object` have the same +meaning as they do for `patch`. + + +patch.dict +========== + +.. function:: patch.dict(in_dict, values=(), clear=False, **kwargs) + + Patch a dictionary, or dictionary like object, and restore the dictionary + to its original state after the test. + + `in_dict` can be a dictionary or a mapping like container. If it is a + mapping then it must at least support getting, setting and deleting items + plus iterating over keys. + + `in_dict` can also be a string specifying the name of the dictionary, which + will then be fetched by importing it. + + `values` can be a dictionary of values to set in the dictionary. `values` + can also be an iterable of `(key, value)` pairs. + + If `clear` is True then the dictionary will be cleared before the new + values are set. + + `patch.dict` can also be called with arbitrary keyword arguments to set + values in the dictionary. + + `patch.dict` can be used as a context manager, decorator or class + decorator. When used as a class decorator `patch.dict` honours + `patch.TEST_PREFIX` for choosing which methods to wrap. + +`patch.dict` can be used to add members to a dictionary, or simply let a test +change a dictionary, and ensure the dictionary is restored when the test +ends. + +.. doctest:: + + >>> from mock import patch + >>> foo = {} + >>> with patch.dict(foo, {'newkey': 'newvalue'}): + ... assert foo == {'newkey': 'newvalue'} + ... + >>> assert foo == {} + + >>> import os + >>> with patch.dict('os.environ', {'newkey': 'newvalue'}): + ... print os.environ['newkey'] + ... + newvalue + >>> assert 'newkey' not in os.environ + +Keywords can be used in the `patch.dict` call to set values in the dictionary: + +.. doctest:: + + >>> mymodule = MagicMock() + >>> mymodule.function.return_value = 'fish' + >>> with patch.dict('sys.modules', mymodule=mymodule): + ... import mymodule + ... mymodule.function('some', 'args') + ... + 'fish' + +`patch.dict` can be used with dictionary like objects that aren't actually +dictionaries. At the very minimum they must support item getting, setting, +deleting and either iteration or membership test. This corresponds to the +magic methods `__getitem__`, `__setitem__`, `__delitem__` and either +`__iter__` or `__contains__`. + +.. doctest:: + + >>> class Container(object): + ... def __init__(self): + ... self.values = {} + ... def __getitem__(self, name): + ... return self.values[name] + ... def __setitem__(self, name, value): + ... self.values[name] = value + ... def __delitem__(self, name): + ... del self.values[name] + ... def __iter__(self): + ... return iter(self.values) + ... + >>> thing = Container() + >>> thing['one'] = 1 + >>> with patch.dict(thing, one=2, two=3): + ... assert thing['one'] == 2 + ... assert thing['two'] == 3 + ... + >>> assert thing['one'] == 1 + >>> assert list(thing) == ['one'] + + +patch.multiple +============== + +.. function:: patch.multiple(target, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs) + + Perform multiple patches in a single call. It takes the object to be + patched (either as an object or a string to fetch the object by importing) + and keyword arguments for the patches:: + + with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'): + ... + + Use :data:`DEFAULT` as the value if you want `patch.multiple` to create + mocks for you. In this case the created mocks are passed into a decorated + function by keyword, and a dictionary is returned when `patch.multiple` is + used as a context manager. + + `patch.multiple` can be used as a decorator, class decorator or a context + manager. The arguments `spec`, `spec_set`, `create`, `autospec` and + `new_callable` have the same meaning as for `patch`. These arguments will + be applied to *all* patches done by `patch.multiple`. + + When used as a class decorator `patch.multiple` honours `patch.TEST_PREFIX` + for choosing which methods to wrap. + +If you want `patch.multiple` to create mocks for you, then you can use +:data:`DEFAULT` as the value. If you use `patch.multiple` as a decorator +then the created mocks are passed into the decorated function by keyword. + +.. doctest:: + + >>> thing = object() + >>> other = object() + + >>> @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) + ... def test_function(thing, other): + ... assert isinstance(thing, MagicMock) + ... assert isinstance(other, MagicMock) + ... + >>> test_function() + +`patch.multiple` can be nested with other `patch` decorators, but put arguments +passed by keyword *after* any of the standard arguments created by `patch`: + +.. doctest:: + + >>> @patch('sys.exit') + ... @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) + ... def test_function(mock_exit, other, thing): + ... assert 'other' in repr(other) + ... assert 'thing' in repr(thing) + ... assert 'exit' in repr(mock_exit) + ... + >>> test_function() + +If `patch.multiple` is used as a context manager, the value returned by the +context manger is a dictionary where created mocks are keyed by name: + +.. doctest:: + + >>> with patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) as values: + ... assert 'other' in repr(values['other']) + ... assert 'thing' in repr(values['thing']) + ... assert values['thing'] is thing + ... assert values['other'] is other + ... + + +.. _start-and-stop: + +patch methods: start and stop +============================= + +All the patchers have `start` and `stop` methods. These make it simpler to do +patching in `setUp` methods or where you want to do multiple patches without +nesting decorators or with statements. + +To use them call `patch`, `patch.object` or `patch.dict` as normal and keep a +reference to the returned `patcher` object. You can then call `start` to put +the patch in place and `stop` to undo it. + +If you are using `patch` to create a mock for you then it will be returned by +the call to `patcher.start`. + +.. doctest:: + + >>> patcher = patch('package.module.ClassName') + >>> from package import module + >>> original = module.ClassName + >>> new_mock = patcher.start() + >>> assert module.ClassName is not original + >>> assert module.ClassName is new_mock + >>> patcher.stop() + >>> assert module.ClassName is original + >>> assert module.ClassName is not new_mock + + +A typical use case for this might be for doing multiple patches in the `setUp` +method of a `TestCase`: + +.. doctest:: + + >>> class MyTest(TestCase): + ... def setUp(self): + ... self.patcher1 = patch('package.module.Class1') + ... self.patcher2 = patch('package.module.Class2') + ... self.MockClass1 = self.patcher1.start() + ... self.MockClass2 = self.patcher2.start() + ... + ... def tearDown(self): + ... self.patcher1.stop() + ... self.patcher2.stop() + ... + ... def test_something(self): + ... assert package.module.Class1 is self.MockClass1 + ... assert package.module.Class2 is self.MockClass2 + ... + >>> MyTest('test_something').run() + +.. caution:: + + If you use this technique you must ensure that the patching is "undone" by + calling `stop`. This can be fiddlier than you might think, because if an + exception is raised in the setUp then tearDown is not called. `unittest2 + `_ cleanup functions make this + easier. + + .. doctest:: + + >>> class MyTest(TestCase): + ... def setUp(self): + ... patcher = patch('package.module.Class') + ... self.MockClass = patcher.start() + ... self.addCleanup(patcher.stop) + ... + ... def test_something(self): + ... assert package.module.Class is self.MockClass + ... + >>> MyTest('test_something').run() + + As an added bonus you no longer need to keep a reference to the `patcher` + object. + +It is also possible to stop all patches which have been started by using +`patch.stopall`. + +.. function:: patch.stopall + + Stop all active patches. Only stops patches started with `start`. + + +TEST_PREFIX +=========== + +All of the patchers can be used as class decorators. When used in this way +they wrap every test method on the class. The patchers recognise methods that +start with `test` as being test methods. This is the same way that the +`unittest.TestLoader` finds test methods by default. + +It is possible that you want to use a different prefix for your tests. You can +inform the patchers of the different prefix by setting `patch.TEST_PREFIX`: + +.. doctest:: + + >>> patch.TEST_PREFIX = 'foo' + >>> value = 3 + >>> + >>> @patch('__main__.value', 'not three') + ... class Thing(object): + ... def foo_one(self): + ... print value + ... def foo_two(self): + ... print value + ... + >>> + >>> Thing().foo_one() + not three + >>> Thing().foo_two() + not three + >>> value + 3 + + +Nesting Patch Decorators +======================== + +If you want to perform multiple patches then you can simply stack up the +decorators. + +You can stack up multiple patch decorators using this pattern: + +.. doctest:: + + >>> @patch.object(SomeClass, 'class_method') + ... @patch.object(SomeClass, 'static_method') + ... def test(mock1, mock2): + ... assert SomeClass.static_method is mock1 + ... assert SomeClass.class_method is mock2 + ... SomeClass.static_method('foo') + ... SomeClass.class_method('bar') + ... return mock1, mock2 + ... + >>> mock1, mock2 = test() + >>> mock1.assert_called_once_with('foo') + >>> mock2.assert_called_once_with('bar') + + +Note that the decorators are applied from the bottom upwards. This is the +standard way that Python applies decorators. The order of the created mocks +passed into your test function matches this order. + +Like all context-managers patches can be nested using contextlib's nested +function; *every* patching will appear in the tuple after "as": + +.. doctest:: + + >>> from contextlib import nested + >>> with nested( + ... patch('package.module.ClassName1'), + ... patch('package.module.ClassName2') + ... ) as (MockClass1, MockClass2): + ... assert package.module.ClassName1 is MockClass1 + ... assert package.module.ClassName2 is MockClass2 + ... + + +.. _where-to-patch: + +Where to patch +============== + +`patch` works by (temporarily) changing the object that a *name* points to with +another one. There can be many names pointing to any individual object, so +for patching to work you must ensure that you patch the name used by the system +under test. + +The basic principle is that you patch where an object is *looked up*, which +is not necessarily the same place as where it is defined. A couple of +examples will help to clarify this. + +Imagine we have a project that we want to test with the following structure:: + + a.py + -> Defines SomeClass + + b.py + -> from a import SomeClass + -> some_function instantiates SomeClass + +Now we want to test `some_function` but we want to mock out `SomeClass` using +`patch`. The problem is that when we import module b, which we will have to +do then it imports `SomeClass` from module a. If we use `patch` to mock out +`a.SomeClass` then it will have no effect on our test; module b already has a +reference to the *real* `SomeClass` and it looks like our patching had no +effect. + +The key is to patch out `SomeClass` where it is used (or where it is looked up +). In this case `some_function` will actually look up `SomeClass` in module b, +where we have imported it. The patching should look like: + + `@patch('b.SomeClass')` + +However, consider the alternative scenario where instead of `from a import +SomeClass` module b does `import a` and `some_function` uses `a.SomeClass`. Both +of these import forms are common. In this case the class we want to patch is +being looked up on the a module and so we have to patch `a.SomeClass` instead: + + `@patch('a.SomeClass')` + + +Patching Descriptors and Proxy Objects +====================================== + +Since version 0.6.0 both patch_ and patch.object_ have been able to correctly +patch and restore descriptors: class methods, static methods and properties. +You should patch these on the *class* rather than an instance. + +Since version 0.7.0 patch_ and patch.object_ work correctly with some objects +that proxy attribute access, like the `django setttings object +`_. + +.. note:: + + In django `import settings` and `from django.conf import settings` + return different objects. If you are using libraries / apps that do both you + may have to patch both. Grrr... diff --git a/python/mock-1.0.0/html/_sources/sentinel.txt b/python/mock-1.0.0/html/_sources/sentinel.txt new file mode 100644 index 00000000000..1c5223da0ed --- /dev/null +++ b/python/mock-1.0.0/html/_sources/sentinel.txt @@ -0,0 +1,58 @@ +========== + Sentinel +========== + + +.. currentmodule:: mock + +.. testsetup:: + + class ProductionClass(object): + def something(self): + return self.method() + + class Test(unittest2.TestCase): + def testSomething(self): + pass + self = Test('testSomething') + + +.. data:: sentinel + + The ``sentinel`` object provides a convenient way of providing unique + objects for your tests. + + Attributes are created on demand when you access them by name. Accessing + the same attribute will always return the same object. The objects + returned have a sensible repr so that test failure messages are readable. + + +.. data:: DEFAULT + + The `DEFAULT` object is a pre-created sentinel (actually + `sentinel.DEFAULT`). It can be used by :attr:`~Mock.side_effect` + functions to indicate that the normal return value should be used. + + +Sentinel Example +================ + +Sometimes when testing you need to test that a specific object is passed as an +argument to another method, or returned. It can be common to create named +sentinel objects to test this. `sentinel` provides a convenient way of +creating and testing the identity of objects like this. + +In this example we monkey patch `method` to return +`sentinel.some_object`: + +.. doctest:: + + >>> real = ProductionClass() + >>> real.method = Mock(name="method") + >>> real.method.return_value = sentinel.some_object + >>> result = real.method() + >>> assert result is sentinel.some_object + >>> sentinel.some_object + sentinel.some_object + + diff --git a/python/mock-1.0.0/html/_static/adctheme.css b/python/mock-1.0.0/html/_static/adctheme.css new file mode 100644 index 00000000000..60395bcefb4 --- /dev/null +++ b/python/mock-1.0.0/html/_static/adctheme.css @@ -0,0 +1,757 @@ +/** + * Sphinx stylesheet -- basic theme + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + h3 { + color:#000000; + font-size: 17px; + margin-bottom:0.5em; + margin-top:2em; + } +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- header ---------------------------------------------------------------- */ + +#header #title { + background:#29334F url(title_background.png) repeat-x scroll 0 0; + border-bottom:1px solid #B6B6B6; + height:25px; + overflow:hidden; +} +#headerButtons { + position: absolute; + list-style: none outside; + top: 26px; + left: 0px; + right: 0px; + margin: 0px; + padding: 0px; + border-top: 1px solid #2B334F; + border-bottom: 1px solid #EDEDED; + height: 20px; + font-size: 8pt; + overflow: hidden; + background-color: #D8D8D8; +} + +#headerButtons li { + background-repeat:no-repeat; + display:inline; + margin-top:0; + padding:0; +} + +.headerButton { + display: inline; + height:20px; +} + +.headerButton a { + text-decoration: none; + float: right; + height: 20px; + padding: 4px 15px; + border-left: 1px solid #ACACAC; + font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; + color: black; +} +.headerButton a:hover { + color: white; + background-color: #787878; + +} + +li#toc_button { + text-align:left; +} + +li#toc_button .headerButton a { + width:198px; + padding-top: 4px; + font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; + color: black; + float: left; + padding-left:15px; + border-right:1px solid #ACACAC; + background:transparent url(triangle_open.png) no-repeat scroll 4px 6px; +} + +li#toc_button .headerButton a:hover { + background-color: #787878; + color: white; +} + +li#page_buttons { +position:absolute; +right:0; +} + +#breadcrumbs { + color: black; + background-image:url(breadcrumb_background.png); + border-top:1px solid #2B334F; + bottom:0; + font-size:10px; + height:15px; + left:0; + overflow:hidden; + padding:3px 10px 0; + position:absolute; + right:0; + white-space:nowrap; + z-index:901; +} +#breadcrumbs a { + color: black; + text-decoration: none; +} +#breadcrumbs a:hover { + text-decoration: underline; +} +#breadcrumbs img { + padding-left: 3px; +} +/* -- sidebar --------------------------------------------------------------- */ +#sphinxsidebar { + position: absolute; + top: 84px; + bottom: 19px; + left: 0px; + width: 229px; + background-color: #E4EBF7; + border-right: 1px solid #ACACAC; + border-top: 1px solid #2B334F; + overflow-x: hidden; + overflow-y: auto; + padding: 0px 0px 0px 0px; + font-size:11px; +} + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +#sphinxsidebar li { + margin: 0px; + padding: 0px; + font-weight: normal; + margin: 0px 0px 7px 0px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 11px; +} + +#sphinxsidebar ul { + list-style: none; + margin: 0px 0px 0px 0px; + padding: 0px 5px 0px 5px; +} + +#sphinxsidebar ul ul, +#sphinxsidebar ul.want-points { + list-style: square; +} + +#sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +#sphinxsidebar form { + margin-top: 10px; +} + +#sphinxsidebar input { + border: 1px solid #787878; + font-family: sans-serif; + font-size: 1em; +} + +img { + border: 0; +} + +#sphinxsidebar li.toctree-l1 a { + font-weight: bold; + color: #000; + text-decoration: none; +} + +#sphinxsidebar li.toctree-l2 a { + font-weight: bold; + color: #4f4f4f; + text-decoration: none; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} +#sphinxsidebar input.prettysearch {border:none;} +input.searchbutton { + float: right; +} +.search-wrapper {width: 100%; height: 25px;} +.search-wrapper input.prettysearch { border: none; width:200px; height: 16px; background: url(searchfield_repeat.png) center top repeat-x; border: 0px; margin: 0; padding: 3px 0 0 0; font: 11px "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif; } +.search-wrapper input.prettysearch { width: 184px; margin-left: 20px; *margin-top:-1px; *margin-right:-2px; *margin-left:10px; } +.search-wrapper .search-left { display: block; position: absolute; width: 20px; height: 19px; background: url(searchfield_leftcap.png) left top no-repeat; } +.search-wrapper .search-right { display: block; position: relative; left: 204px; top: -19px; width: 10px; height: 19px; background: url(searchfield_rightcap.png) right top no-repeat; } + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +/* -- general body styles --------------------------------------------------- */ +.document { + border-top:1px solid #2B334F; + overflow:auto; + padding-left:2em; + padding-right:2em; + position:absolute; + z-index:1; + top:84px; + bottom:19px; + right:0; + left:230px; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +/* -- sidebars -------------------------------------------------------------- */ + +/*div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} +*/ +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ +.admonition { + border: 1px solid #a1a5a9; + background-color: #f7f7f7; + margin: 20px; + padding: 0px 8px 7px 9px; + text-align: left; +} +.warning { + background-color:#E8E8E8; + border:1px solid #111111; + margin:30px; +} +.admonition p { + font: 12px 'Lucida Grande', Geneva, Helvetica, Arial, sans-serif; + margin-top: 7px; + margin-bottom: 0px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; + padding-top: 3px; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border-collapse: collapse; + border-top: 1px solid #919699; + border-left: 1px solid #919699; + border-right: 1px solid #919699; + font-size:12px; + padding:8px; + text-align:left; + vertical-align:top; +} + +table.docutils td, table.docutils th { + padding: 8px; + font-size: 12px; + text-align: left; + vertical-align: top; + border-bottom: 1px solid #919699; +} + +table.docutils th { + font-weight: bold; +} +/* This alternates colors in up to six table rows (light blue for odd, white for even)*/ +.docutils tr { + background: #F0F5F9; +} + +.docutils tr + tr { + background: #FFFFFF; +} + +.docutils tr + tr + tr { + background: #F0F5F9; +} + +.docutils tr + tr + tr + tr { + background: #FFFFFF; +} + +.docutils tr + tr + tr +tr + tr { + background: #F0F5F9; +} + +.docutils tr + tr + tr + tr + tr + tr { + background: #FFFFFF; +} + +.docutils tr + tr + tr + tr + tr + tr + tr { + background: #F0F5F9; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +/* -- other body styles ----------------------------------------------------- */ + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; + font-size: 12px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; + font-size: 12px; +} + +dt:target, .highlight { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 0.8em; +} + +dl.glossary dd { + font-size:12px; +} +.field-list ul { + vertical-align: top; + margin: 0; + padding-bottom: 0; + list-style: none inside; +} + +.field-list ul li { + margin-top: 0; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + background-color:#F1F5F9; + border:1px solid #C9D1D7; + border-spacing:0; + font-family:"Bitstream Vera Sans Mono",Monaco,"Lucida Console",Courier,Consolas,monospace; + font-size:11px; + padding: 10px; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt { + font-family:"Bitstream Vera Sans Mono",Monaco,"Lucida Console",Courier,Consolas,monospace; + +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} + +body { + font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +} + +dl.class dt { + padding: 3px; +/* border-top: 2px solid #999;*/ +} + +em.property { + font-style: normal; +} + +dl.class dd p { + margin-top: 6px; +} + +dl.class dd dl.exception dt { + padding: 3px; + background-color: #FFD6D6; + border-top: none; +} + +dl.class dd dl.method dt { + padding: 3px; + background-color: #e9e9e9; + border-top: none; + +} + +dl.function dt { + padding: 3px; + border-top: 2px solid #999; +} + +ul { +list-style-image:none; +list-style-position:outside; +list-style-type:square; +margin:0 0 0 30px; +padding:0 0 12px 6px; +} +#docstitle { + height: 36px; + background-image: url(header_sm_mid.png); + left: 0; + top: 0; + position: absolute; + width: 100%; +} +#docstitle p { + padding:7px 0 0 45px; + margin: 0; + color: white; + text-shadow:0 1px 0 #787878; + background: transparent url(documentation.png) no-repeat scroll 10px 3px; + height: 36px; + font-size: 15px; +} +#header { +height:45px; +left:0; +position:absolute; +right:0; +top:36px; +z-index:900; +} + +#header h1 { +font-size:10pt; +margin:0; +padding:5px 0 0 10px; +text-shadow:0 1px 0 #D5D5D5; +white-space:nowrap; +} + +h1 { +-x-system-font:none; +color:#000000; +font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +font-size:30px; +font-size-adjust:none; +font-stretch:normal; +font-style:normal; +font-variant:normal; +font-weight:bold; +line-height:normal; +margin-bottom:25px; +margin-top:1em; +} + +.footer { +border-top:1px solid #DDDDDD; +clear:both; +padding-top:9px; +width:100%; +font-size:10px; +} + +p { +-x-system-font:none; +font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +font-size:12px; +font-size-adjust:none; +font-stretch:normal; +font-style:normal; +font-variant:normal; +font-weight:normal; +line-height:normal; +margin-bottom:10px; +margin-top:0; +} + +h2 { +border-bottom:1px solid #919699; +color:#000000; +font-size:24px; +margin-top:2.5em; +padding-bottom:2px; +} + +a:link:hover { +color:#093D92; +text-decoration:underline; +} + +a:link { +color:#093D92; +text-decoration:none; +} + + +ol { +list-style-position:outside; +list-style-type:decimal; +margin:0 0 0 30px; +padding:0 0 12px 6px; +} +li { +margin-top:7px; +font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +font-size:12px; +font-size-adjust:none; +font-stretch:normal; +font-style:normal; +font-variant:normal; +font-weight:normal; +line-height:normal; +} +li p { +margin-top:8px; +} \ No newline at end of file diff --git a/python/mock-1.0.0/html/_static/basic.css b/python/mock-1.0.0/html/_static/basic.css new file mode 100644 index 00000000000..43e8bafaf35 --- /dev/null +++ b/python/mock-1.0.0/html/_static/basic.css @@ -0,0 +1,540 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + width: 30px; +} + +img { + border: 0; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/python/mock-1.0.0/html/_static/breadcrumb_background.png b/python/mock-1.0.0/html/_static/breadcrumb_background.png new file mode 100644 index 0000000000000000000000000000000000000000..9b45910e0b984604c5e8b913f431bad172bdef53 GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^j6f{R!3HD)xzimdKI;Vst0CaOHKL7v# literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/_static/default.css b/python/mock-1.0.0/html/_static/default.css new file mode 100644 index 00000000000..2a3ac133162 --- /dev/null +++ b/python/mock-1.0.0/html/_static/default.css @@ -0,0 +1,256 @@ +/* + * default.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- default theme. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +tt { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th { + background-color: #ede; +} + +.warning tt { + background: #efc2c2; +} + +.note tt { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} \ No newline at end of file diff --git a/python/mock-1.0.0/html/_static/doctools.js b/python/mock-1.0.0/html/_static/doctools.js new file mode 100644 index 00000000000..d4619fdfb10 --- /dev/null +++ b/python/mock-1.0.0/html/_static/doctools.js @@ -0,0 +1,247 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +} + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * small function to check if an array contains + * a given item. + */ +jQuery.contains = function(arr, item) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] == item) + return true; + } + return false; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('

    ') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/python/mock-1.0.0/html/_static/documentation.png b/python/mock-1.0.0/html/_static/documentation.png new file mode 100644 index 0000000000000000000000000000000000000000..f0d334b57a69d1c49ce833d2d3536ececbf31c2c GIT binary patch literal 412 zcmV;N0b~A&P)V|5Xcvp(%M!Kgji)eD=qwh zg{)PY6mE9sSonm;^7iSXc>&$i3^Q|Q&b@P+33{MD0iFXj;0!p8h+n&qyCPF6JQV6iHPIexmn;9Fb7Nl<3Jl|0!P3BP>+aqQH5_(N#jt7nHQ%jqyU$Fz7$7E>_1_sYph)S z-zvU;;z5rq6AzyH?u!RKu1s9{Qx9ijQN?HKDbR|DG2kh;m|&6k2-pJlBccKP<>HGw z4ZH?E0E=DsG=W{94m5xVzyvT4ECVxzcJ)j45&Nn>F3VIMR^O(SeyAPwyZWLeeq8;O zQaa2W-}0Lq>ROqwM*>TI#Mji%>SliNQ9sg~pgye1RmeF;a#_fxJ%6YG0000OL$D9)yc9|lc|nKf<9@eUiWd>3GuTC!a5vdfWYEazjncPj5ZQX%+1 zt8B*4=d)!cdDz4wr^#OMYfqGz$1LDFF>|#>*O?AGil(WEs?wLLy{Gj2J_@opDm%`dlax3yA*@*N$G&*ukFv>P8+2CBWO(qz zD0k1@kN>hhb1_6`&wrCswzINE(evt-5C1B^STi2@PmdKI;Vst0PQB6!2kdN literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/_static/header_sm_mid.png b/python/mock-1.0.0/html/_static/header_sm_mid.png new file mode 100644 index 0000000000000000000000000000000000000000..dce5a40e98bc982ba4733bb0797230cc98a69f01 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^>_Du-!2~2vacq|XQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS>JiX1&%978H@CGnhJm3n|dhv)zQWWm4R-|L56tdDT{|2W{c z`088rM^^v!-XmRb(=9yy&tL0^Shqb)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
    a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

    ";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
    ";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
    ","
    "];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
    ").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
    "; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/python/mock-1.0.0/html/_static/minus.png b/python/mock-1.0.0/html/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..da1c5620d10c047525a467a425abe9ff5269cfc2 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1SHkYJtzcHoCO|{#XvD(5N2eUHAey{$X?>< z>&kweokM_|(Po{+Q=kw>iEBiObAE1aYF-J$w=>iB1I2R$WLpMkF=>bh=@O1TaS?83{1OVknK< z>&kweokM`jkU7Va11Q8%;u=xnoS&PUnpeW`?aZ|OK(QcC7sn8Z%gHvy&v=;Q4jejg zV8NnAO`-4Z@2~&zopr02WF_WB>pF literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/_static/pygments.css b/python/mock-1.0.0/html/_static/pygments.css new file mode 100644 index 00000000000..f07b654ba27 --- /dev/null +++ b/python/mock-1.0.0/html/_static/pygments.css @@ -0,0 +1,62 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f0f0f0; } +.highlight .c { color: #60a0b0; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #808080 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0040D0 } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #40a070 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #40a070 } /* Literal.Number.Float */ +.highlight .mh { color: #40a070 } /* Literal.Number.Hex */ +.highlight .mi { color: #40a070 } /* Literal.Number.Integer */ +.highlight .mo { color: #40a070 } /* Literal.Number.Oct */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/python/mock-1.0.0/html/_static/scrn1.png b/python/mock-1.0.0/html/_static/scrn1.png new file mode 100644 index 0000000000000000000000000000000000000000..6499b3cf760c9d065408842221161d014fb7a073 GIT binary patch literal 108046 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�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 ZGnh*?r5D zK?F?^1PO^?Wsw#|C6=?-W^S0-j0f&L_uPHXI&1H}_EzfkIyH{f)m7!>^Kb(vi>SKQho4r_&jBxm=pvZl})9PBIt_?E_{q z8Rhi!G*BoMepoD)w<9AXn%nIro6RO*nx+X@JAfKq7Z(@yVzKz$>-D}L9UUc)$3tCR zUDVOhL2$KQcLi`|Wko$YI{Ltj-i1OT>h0|%W=ZTmEiW(A?(Qye7|8GU6YP;lgnD{< zUIsirKXhlgU1Mx%0h^H4ePZyXK>X88d?&bEAykB5T^5)*LGsP|MCH)U&5g`hV+;=uzbGS( zZ^~4w)sTK8m`+Sg5SJcpZEZ<1ng?PyGc!XRs%JLC{QUeYHpIHSyH#DpX0ut6V6|F_ zlSbFq*ThPo$;nCL+9s|l(I?i3?~~F?rBbh1Q&~=c4FGTjY?-yVdUtn6wOUPLh%9qe zQ8JmNrGMRELr%h4@nWTM!3_ZyKtc-)G|T1avEu822WQ%mvv4FO#qs` BFGc_W literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/_static/searchfield_rightcap.png b/python/mock-1.0.0/html/_static/searchfield_rightcap.png new file mode 100644 index 0000000000000000000000000000000000000000..8e13620ecbc56b3464c48d6cd09bf1337e1c33a6 GIT binary patch literal 530 zcmV+t0`2{YP)`}SW{%?kjooesx~_vP%P<%W?AdJgh{{)$N=4@w2Hn_CKTAY65m^iR+I*Mbk6^ z``^NHsdV!wP>EqmHX4oOX0v%l>il0RsWXek!jL5C0oS5;;H%I;7qQ7?Vs$#5r$(c3 zLKkVc?jhG;Dl&tEW>67q;r<#eHuX!@@AqBfuGeb~jqr-VJ=U*XtycBJC;bR809Q3H UB&DjC!~g&Q07*qoM6N<$f;HIhQUCw| literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/_static/searchtools.js b/python/mock-1.0.0/html/_static/searchtools.js new file mode 100644 index 00000000000..663be4c909b --- /dev/null +++ b/python/mock-1.0.0/html/_static/searchtools.js @@ -0,0 +1,560 @@ +/* + * searchtools.js_t + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for the full-text search. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words, hlwords is the list of normal, unstemmed + * words. the first one is used to find the occurance, the + * latter for highlighting it. + */ + +jQuery.makeSearchSummary = function(text, keywords, hlwords) { + var textLower = text.toLowerCase(); + var start = 0; + $.each(keywords, function() { + var i = textLower.indexOf(this.toLowerCase()); + if (i > -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('
    ').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; +} + + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, success: null, + dataType: "script", cache: true}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (var i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something + */ + performSearch : function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('

    ' + _('Searching') + '

    ').appendTo(this.out); + this.dots = $('').appendTo(this.title); + this.status = $('

    ').appendTo(this.out); + this.output = $('

    bA=H)-LUlSX=-5xJ#VDlxH95lK$W%UCgH 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 Ca&-Uz literal 0 HcmV?d00001 diff --git a/python/mock-1.0.0/html/.doctrees/mock.doctree b/python/mock-1.0.0/html/.doctrees/mock.doctree new file mode 100644 index 0000000000000000000000000000000000000000..44dc3043ceec92dc9fe9685ae682eda8c12cf95d GIT binary patch literal 152111 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