SoftVPX.cpp revision 84333e0475bc911adc16417f4ca327c975cf6c36
1bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber/* 2bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * Copyright (C) 2011 The Android Open Source Project 3bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * 4bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * Licensed under the Apache License, Version 2.0 (the "License"); 5bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * you may not use this file except in compliance with the License. 6bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * You may obtain a copy of the License at 7bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * 8bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * http://www.apache.org/licenses/LICENSE-2.0 9bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * 10bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * Unless required by applicable law or agreed to in writing, software 11bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * distributed under the License is distributed on an "AS IS" BASIS, 12bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * See the License for the specific language governing permissions and 14bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber * limitations under the License. 15bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber */ 16bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 17bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber//#define LOG_NDEBUG 0 18bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber#define LOG_TAG "SoftVPX" 19bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber#include <utils/Log.h> 20bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 21bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber#include "SoftVPX.h" 22bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 23bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber#include <media/stagefright/foundation/ADebug.h> 24bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber#include <media/stagefright/MediaDefs.h> 25bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 26bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber#include "vpx/vpx_decoder.h" 27bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber#include "vpx/vpx_codec.h" 28bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber#include "vpx/vp8dx.h" 29bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 30bbba88cb1bdc34705d1477208990a06904c022e7Andreas Hubernamespace android { 31bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 32bbba88cb1bdc34705d1477208990a06904c022e7Andreas HuberSoftVPX::SoftVPX( 33bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber const char *name, 3494705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang const char *componentRole, 3594705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang OMX_VIDEO_CODINGTYPE codingType, 36bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber const OMX_CALLBACKTYPE *callbacks, 37bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber OMX_PTR appData, 38bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber OMX_COMPONENTTYPE **component) 397f616d3cc5366a4b8af20d3d0c768e3de1df0666Lajos Molnar : SoftVideoDecoderOMXComponent( 4094705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang name, componentRole, codingType, 417f616d3cc5366a4b8af20d3d0c768e3de1df0666Lajos Molnar NULL /* profileLevels */, 0 /* numProfileLevels */, 427f616d3cc5366a4b8af20d3d0c768e3de1df0666Lajos Molnar 320 /* width */, 240 /* height */, callbacks, appData, component), 4394705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang mMode(codingType == OMX_VIDEO_CodingVP8 ? MODE_VP8 : MODE_VP9), 447f616d3cc5366a4b8af20d3d0c768e3de1df0666Lajos Molnar mCtx(NULL) { 457f616d3cc5366a4b8af20d3d0c768e3de1df0666Lajos Molnar initPorts(kNumBuffers, 768 * 1024 /* inputBufferSize */, 4694705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang kNumBuffers, 4794705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang codingType == OMX_VIDEO_CodingVP8 ? MEDIA_MIMETYPE_VIDEO_VP8 : MEDIA_MIMETYPE_VIDEO_VP9); 487f616d3cc5366a4b8af20d3d0c768e3de1df0666Lajos Molnar 49bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber CHECK_EQ(initDecoder(), (status_t)OK); 50bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber} 51bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 52bbba88cb1bdc34705d1477208990a06904c022e7Andreas HuberSoftVPX::~SoftVPX() { 53bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber vpx_codec_destroy((vpx_codec_ctx_t *)mCtx); 54bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber delete (vpx_codec_ctx_t *)mCtx; 55bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber mCtx = NULL; 56bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber} 57bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 58f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dongstatic int GetCPUCoreCount() { 59f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong int cpuCoreCount = 1; 60f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong#if defined(_SC_NPROCESSORS_ONLN) 61f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); 62f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong#else 63f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong // _SC_NPROC_ONLN must be defined... 64f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong cpuCoreCount = sysconf(_SC_NPROC_ONLN); 65f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong#endif 66f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong CHECK(cpuCoreCount >= 1); 673856b090cd04ba5dd4a59a12430ed724d5995909Steve Block ALOGV("Number of CPU cores: %d", cpuCoreCount); 68f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong return cpuCoreCount; 69f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong} 70f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong 71bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huberstatus_t SoftVPX::initDecoder() { 72bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber mCtx = new vpx_codec_ctx_t; 73bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber vpx_codec_err_t vpx_err; 74f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong vpx_codec_dec_cfg_t cfg; 75f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong memset(&cfg, 0, sizeof(vpx_codec_dec_cfg_t)); 76f3ac3e3c94c14dbf1cdf6a4577f0b3aa8edfad06James Dong cfg.threads = GetCPUCoreCount(); 77bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber if ((vpx_err = vpx_codec_dec_init( 7894705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang (vpx_codec_ctx_t *)mCtx, 7994705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang mMode == MODE_VP8 ? &vpx_codec_vp8_dx_algo : &vpx_codec_vp9_dx_algo, 8094705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang &cfg, 0))) { 8129357bc2c0dd7c43ad3bd0c8e3efa4e6fd9bfd47Steve Block ALOGE("on2 decoder failed to initialize. (%d)", vpx_err); 82bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber return UNKNOWN_ERROR; 83bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber } 84bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 85bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber return OK; 86bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber} 87bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 8884333e0475bc911adc16417f4ca327c975cf6c36Andreas Hubervoid SoftVPX::onQueueFilled(OMX_U32 /* portIndex */) { 89bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber if (mOutputPortSettingsChange != NONE) { 90bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber return; 91bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber } 92bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 93bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber List<BufferInfo *> &inQueue = getPortQueue(0); 94bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber List<BufferInfo *> &outQueue = getPortQueue(1); 95a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar bool EOSseen = false; 96bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 97bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber while (!inQueue.empty() && !outQueue.empty()) { 98bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber BufferInfo *inInfo = *inQueue.begin(); 99bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader; 100bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 101bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber BufferInfo *outInfo = *outQueue.begin(); 102bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader; 103bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 104bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) { 105a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar EOSseen = true; 106a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar if (inHeader->nFilledLen == 0) { 107a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar inQueue.erase(inQueue.begin()); 108a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar inInfo->mOwnedByUs = false; 109a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar notifyEmptyBufferDone(inHeader); 110a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar 111a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar outHeader->nFilledLen = 0; 112a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar outHeader->nFlags = OMX_BUFFERFLAG_EOS; 113a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar 114a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar outQueue.erase(outQueue.begin()); 115a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar outInfo->mOwnedByUs = false; 116a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar notifyFillBufferDone(outHeader); 117a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar return; 118a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar } 119bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber } 120bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 121bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber if (vpx_codec_decode( 122bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber (vpx_codec_ctx_t *)mCtx, 123bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber inHeader->pBuffer + inHeader->nOffset, 124bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber inHeader->nFilledLen, 125bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber NULL, 126bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 0)) { 12729357bc2c0dd7c43ad3bd0c8e3efa4e6fd9bfd47Steve Block ALOGE("on2 decoder failed to decode frame."); 128bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 129bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); 130bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber return; 131bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber } 132bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 133bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber vpx_codec_iter_t iter = NULL; 134bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber vpx_image_t *img = vpx_codec_get_frame((vpx_codec_ctx_t *)mCtx, &iter); 135bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 136bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber if (img != NULL) { 137bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber CHECK_EQ(img->fmt, IMG_FMT_I420); 138bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 1397f616d3cc5366a4b8af20d3d0c768e3de1df0666Lajos Molnar uint32_t width = img->d_w; 1407f616d3cc5366a4b8af20d3d0c768e3de1df0666Lajos Molnar uint32_t height = img->d_h; 141bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 142bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber if (width != mWidth || height != mHeight) { 143bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber mWidth = width; 144bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber mHeight = height; 145bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 146bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber updatePortDefinitions(); 147bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 148bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber notify(OMX_EventPortSettingsChanged, 1, 0, NULL); 149bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber mOutputPortSettingsChange = AWAITING_DISABLED; 150bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber return; 151bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber } 152bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 153bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber outHeader->nOffset = 0; 154bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber outHeader->nFilledLen = (width * height * 3) / 2; 155a02eae5e911f3bdc3f84f39c0ef223261b646128Lajos Molnar outHeader->nFlags = EOSseen ? OMX_BUFFERFLAG_EOS : 0; 156bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber outHeader->nTimeStamp = inHeader->nTimeStamp; 157bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 158bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber const uint8_t *srcLine = (const uint8_t *)img->planes[PLANE_Y]; 159bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber uint8_t *dst = outHeader->pBuffer; 160bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber for (size_t i = 0; i < img->d_h; ++i) { 161bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber memcpy(dst, srcLine, img->d_w); 162bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 163bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber srcLine += img->stride[PLANE_Y]; 164bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber dst += img->d_w; 165bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber } 166bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 167bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber srcLine = (const uint8_t *)img->planes[PLANE_U]; 168bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber for (size_t i = 0; i < img->d_h / 2; ++i) { 169bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber memcpy(dst, srcLine, img->d_w / 2); 170bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 171bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber srcLine += img->stride[PLANE_U]; 172bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber dst += img->d_w / 2; 173bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber } 174bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 175bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber srcLine = (const uint8_t *)img->planes[PLANE_V]; 176bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber for (size_t i = 0; i < img->d_h / 2; ++i) { 177bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber memcpy(dst, srcLine, img->d_w / 2); 178bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 179bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber srcLine += img->stride[PLANE_V]; 180bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber dst += img->d_w / 2; 181bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber } 182bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 183bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber outInfo->mOwnedByUs = false; 184bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber outQueue.erase(outQueue.begin()); 185bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber outInfo = NULL; 186bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber notifyFillBufferDone(outHeader); 187bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber outHeader = NULL; 188bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber } 189bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 190bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber inInfo->mOwnedByUs = false; 191bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber inQueue.erase(inQueue.begin()); 192bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber inInfo = NULL; 193bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber notifyEmptyBufferDone(inHeader); 194bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber inHeader = NULL; 195bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber } 196bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber} 197bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 198bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber} // namespace android 199bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber 200bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huberandroid::SoftOMXComponent *createSoftOMXComponent( 201bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber const char *name, const OMX_CALLBACKTYPE *callbacks, 202bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber OMX_PTR appData, OMX_COMPONENTTYPE **component) { 20394705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang if (!strcmp(name, "OMX.google.vp8.decoder")) { 20494705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang return new android::SoftVPX( 20594705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang name, "video_decoder.vp8", OMX_VIDEO_CodingVP8, 20694705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang callbacks, appData, component); 20794705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang } else if (!strcmp(name, "OMX.google.vp9.decoder")) { 20894705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang return new android::SoftVPX( 20994705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang name, "video_decoder.vp9", OMX_VIDEO_CodingVP9, 21094705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang callbacks, appData, component); 21194705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang } else { 21294705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang CHECK(!"Unknown component"); 21394705aff3c9eef58cbb72ec6fe5d2dcfd9481646hkuang } 214bbba88cb1bdc34705d1477208990a06904c022e7Andreas Huber} 215