1 /* 2 * Copyright 2011 The Android Open Source Project 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #define LOG_TAG "VideoLayerManager" 27 #define LOG_NDEBUG 1 28 29 #include "config.h" 30 #include "VideoLayerManager.h" 31 32 #include "AndroidLog.h" 33 #include "RenderSkinMediaButton.h" 34 #include "SkCanvas.h" 35 #include <wtf/CurrentTime.h> 36 37 #if USE(ACCELERATED_COMPOSITING) 38 39 // The animation of the play/pause icon will last for PLAY_PAUSE_ICON_SHOW_TIME 40 // seconds. 41 #define PLAY_PAUSE_ICON_SHOW_TIME 1 42 43 // Define the max sum of all the video's sizes. 44 // Note that video_size = width * height. If there is no compression, then the 45 // maximum memory consumption could be 4 * video_size. 46 // Setting this to 2M, means that maximum memory consumption of all the 47 // screenshots would not be above 8M. 48 #define MAX_VIDEOSIZE_SUM 2097152 49 50 // We don't preload the video data, so we don't have the exact size yet. 51 // Assuming 16:9 by default, this will be corrected after video prepared. 52 #define DEFAULT_VIDEO_ASPECT_RATIO 1.78 53 54 #define VIDEO_TEXTURE_NUMBER 5 55 #define VIDEO_BUTTON_SIZE 64 56 57 namespace WebCore { 58 59 VideoLayerManager::VideoLayerManager() 60 : m_currentTimeStamp(0) 61 , m_createdTexture(false) 62 , m_posterTextureId(0) 63 , m_spinnerOuterTextureId(0) 64 , m_spinnerInnerTextureId(0) 65 , m_playTextureId(0) 66 , m_pauseTextureId(0) 67 , m_buttonRect(0, 0, VIDEO_BUTTON_SIZE, VIDEO_BUTTON_SIZE) 68 { 69 } 70 71 int VideoLayerManager::getButtonSize() 72 { 73 return VIDEO_BUTTON_SIZE; 74 } 75 76 GLuint VideoLayerManager::createTextureFromImage(RenderSkinMediaButton::MediaButton buttonType) 77 { 78 SkRect rect = SkRect(m_buttonRect); 79 SkBitmap bitmap; 80 bitmap.setConfig(SkBitmap::kARGB_8888_Config, rect.width(), rect.height()); 81 bitmap.allocPixels(); 82 bitmap.eraseColor(0); 83 84 SkCanvas canvas(bitmap); 85 canvas.drawARGB(0, 0, 0, 0, SkXfermode::kClear_Mode); 86 RenderSkinMediaButton::Draw(&canvas, m_buttonRect, buttonType, true, false); 87 88 GLuint texture; 89 glGenTextures(1, &texture); 90 91 GLUtils::createTextureWithBitmap(texture, bitmap); 92 bitmap.reset(); 93 return texture; 94 } 95 96 // Should be called at the VideoLayerAndroid::drawGL to make sure we allocate 97 // the GL resources lazily. 98 void VideoLayerManager::initGLResourcesIfNeeded() 99 { 100 if (!m_createdTexture) { 101 ALOGD("Reinit GLResource for VideoLayer"); 102 initGLResources(); 103 } 104 } 105 106 void VideoLayerManager::initGLResources() 107 { 108 GLUtils::checkGlError("before initGLResources()"); 109 m_spinnerOuterTextureId = 110 createTextureFromImage(RenderSkinMediaButton::SPINNER_OUTER); 111 m_spinnerInnerTextureId = 112 createTextureFromImage(RenderSkinMediaButton::SPINNER_INNER); 113 m_posterTextureId = 114 createTextureFromImage(RenderSkinMediaButton::VIDEO); 115 m_playTextureId = createTextureFromImage(RenderSkinMediaButton::PLAY); 116 m_pauseTextureId = createTextureFromImage(RenderSkinMediaButton::PAUSE); 117 118 m_createdTexture = !GLUtils::checkGlError("initGLResources()"); 119 return; 120 } 121 122 void VideoLayerManager::cleanupGLResources() 123 { 124 if (m_createdTexture) { 125 GLuint videoTextures[VIDEO_TEXTURE_NUMBER] = { m_spinnerOuterTextureId, 126 m_spinnerInnerTextureId, m_posterTextureId, m_playTextureId, 127 m_pauseTextureId }; 128 129 glDeleteTextures(VIDEO_TEXTURE_NUMBER, videoTextures); 130 m_createdTexture = false; 131 } 132 // Delete the texture in retired mode, but have not hit draw call to be 133 // removed. 134 deleteUnusedTextures(); 135 136 // Go over the registered GL textures (screen shot textures) and delete them. 137 android::Mutex::Autolock lock(m_videoLayerInfoMapLock); 138 InfoIterator end = m_videoLayerInfoMap.end(); 139 for (InfoIterator it = m_videoLayerInfoMap.begin(); it != end; ++it) { 140 // The map include every video has been played, so their textureId can 141 // be deleted already, like hitting onTrimMemory multiple times. 142 if (it->second->textureId) { 143 ALOGV("delete texture from the map %d", it->second->textureId); 144 glDeleteTextures(1, &it->second->textureId); 145 // Set the textureID to 0 to show the video icon. 146 it->second->textureId = 0; 147 } 148 } 149 150 GLUtils::checkGlError("cleanupGLResources()"); 151 return; 152 } 153 154 // Getting TextureId for GL draw call, in the UI thread. 155 GLuint VideoLayerManager::getTextureId(const int layerId) 156 { 157 android::Mutex::Autolock lock(m_videoLayerInfoMapLock); 158 GLuint result = 0; 159 if (m_videoLayerInfoMap.contains(layerId)) 160 result = m_videoLayerInfoMap.get(layerId)->textureId; 161 return result; 162 } 163 164 // Getting the aspect ratio for GL draw call, in the UI thread. 165 float VideoLayerManager::getAspectRatio(const int layerId) 166 { 167 android::Mutex::Autolock lock(m_videoLayerInfoMapLock); 168 float result = 0; 169 if (m_videoLayerInfoMap.contains(layerId)) 170 result = m_videoLayerInfoMap.get(layerId)->aspectRatio; 171 return result; 172 } 173 174 // Getting matrix for GL draw call, in the UI thread. 175 GLfloat* VideoLayerManager::getMatrix(const int layerId) 176 { 177 android::Mutex::Autolock lock(m_videoLayerInfoMapLock); 178 GLfloat* result = 0; 179 if (m_videoLayerInfoMap.contains(layerId)) 180 result = m_videoLayerInfoMap.get(layerId)->surfaceMatrix; 181 return result; 182 } 183 184 int VideoLayerManager::getTotalMemUsage() 185 { 186 int sum = 0; 187 InfoIterator end = m_videoLayerInfoMap.end(); 188 for (InfoIterator it = m_videoLayerInfoMap.begin(); it != end; ++it) 189 sum += it->second->videoSize; 190 return sum; 191 } 192 193 // When the video start, we know its texture info, so we register when we 194 // recieve the setSurfaceTexture call, this happens on UI thread. 195 void VideoLayerManager::registerTexture(const int layerId, const GLuint textureId) 196 { 197 android::Mutex::Autolock lock(m_videoLayerInfoMapLock); 198 // If the texture has been registered, then early return. 199 if (m_videoLayerInfoMap.get(layerId)) { 200 GLuint oldTextureId = m_videoLayerInfoMap.get(layerId)->textureId; 201 if (oldTextureId != textureId) 202 removeLayerInternal(layerId); 203 else 204 return; 205 } 206 // The old info is deleted and now complete the new info and store it. 207 VideoLayerInfo* pInfo = new VideoLayerInfo(); 208 pInfo->textureId = textureId; 209 memset(pInfo->surfaceMatrix, 0, sizeof(pInfo->surfaceMatrix)); 210 pInfo->videoSize = 0; 211 pInfo->aspectRatio = DEFAULT_VIDEO_ASPECT_RATIO; 212 m_currentTimeStamp++; 213 pInfo->timeStamp = m_currentTimeStamp; 214 pInfo->lastIconShownTime = 0; 215 pInfo->iconState = Registered; 216 217 m_videoLayerInfoMap.add(layerId, pInfo); 218 ALOGV("GL texture %d regisered for layerId %d", textureId, layerId); 219 220 return; 221 } 222 223 // Only when the video is prepared, we got the video size. So we should update 224 // the size for the video accordingly. 225 // This is called from webcore thread, from MediaPlayerPrivateAndroid. 226 void VideoLayerManager::updateVideoLayerSize(const int layerId, const int size, 227 const float ratio) 228 { 229 android::Mutex::Autolock lock(m_videoLayerInfoMapLock); 230 if (m_videoLayerInfoMap.contains(layerId)) { 231 VideoLayerInfo* pInfo = m_videoLayerInfoMap.get(layerId); 232 if (pInfo) { 233 pInfo->videoSize = size; 234 pInfo->aspectRatio = ratio; 235 } 236 } 237 238 // If the memory usage is out of bound, then just delete the oldest ones. 239 // Because we only recycle the texture before the current timestamp, the 240 // current video's texture will not be deleted. 241 while (getTotalMemUsage() > MAX_VIDEOSIZE_SUM) 242 if (!recycleTextureMem()) 243 break; 244 return; 245 } 246 247 // This is called only from UI thread, at drawGL time. 248 void VideoLayerManager::updateMatrix(const int layerId, const GLfloat* matrix) 249 { 250 android::Mutex::Autolock lock(m_videoLayerInfoMapLock); 251 if (m_videoLayerInfoMap.contains(layerId)) { 252 // If the existing video layer's matrix is matching the incoming one, 253 // then skip the update. 254 VideoLayerInfo* pInfo = m_videoLayerInfoMap.get(layerId); 255 ASSERT(matrix); 256 if (pInfo && !memcmp(matrix, pInfo->surfaceMatrix, sizeof(pInfo->surfaceMatrix))) 257 return; 258 memcpy(pInfo->surfaceMatrix, matrix, sizeof(pInfo->surfaceMatrix)); 259 } else { 260 ALOGV("Error: should not reach here, the layerId %d should exist!", layerId); 261 ASSERT(false); 262 } 263 return; 264 } 265 266 // This is called on the webcore thread, save the GL texture for recycle in 267 // the retired queue. They will be deleted in deleteUnusedTextures() in the UI 268 // thread. 269 // Return true when we found one texture to retire. 270 bool VideoLayerManager::recycleTextureMem() 271 { 272 // Find the oldest texture int the m_videoLayerInfoMap, put it in m_retiredTextures 273 int oldestTimeStamp = m_currentTimeStamp; 274 int oldestLayerId = -1; 275 276 InfoIterator end = m_videoLayerInfoMap.end(); 277 #ifdef DEBUG 278 ALOGV("VideoLayerManager::recycleTextureMem m_videoLayerInfoMap contains"); 279 for (InfoIterator it = m_videoLayerInfoMap.begin(); it != end; ++it) 280 ALOGV(" layerId %d, textureId %d, videoSize %d, timeStamp %d ", 281 it->first, it->second->textureId, it->second->videoSize, it->second->timeStamp); 282 #endif 283 for (InfoIterator it = m_videoLayerInfoMap.begin(); it != end; ++it) { 284 if (it->second->timeStamp < oldestTimeStamp) { 285 oldestTimeStamp = it->second->timeStamp; 286 oldestLayerId = it->first; 287 } 288 } 289 290 bool foundTextureToRetire = (oldestLayerId != -1); 291 if (foundTextureToRetire) 292 removeLayerInternal(oldestLayerId); 293 294 return foundTextureToRetire; 295 } 296 297 // This is only called in the UI thread, b/c glDeleteTextures need to be called 298 // on the right context. 299 void VideoLayerManager::deleteUnusedTextures() 300 { 301 m_retiredTexturesLock.lock(); 302 int size = m_retiredTextures.size(); 303 if (size > 0) { 304 GLuint* textureNames = new GLuint[size]; 305 int index = 0; 306 Vector<GLuint>::const_iterator end = m_retiredTextures.end(); 307 for (Vector<GLuint>::const_iterator it = m_retiredTextures.begin(); 308 it != end; ++it) { 309 GLuint textureName = *it; 310 if (textureName) { 311 textureNames[index] = textureName; 312 index++; 313 ALOGV("GL texture %d will be deleted", textureName); 314 } 315 } 316 glDeleteTextures(size, textureNames); 317 delete textureNames; 318 m_retiredTextures.clear(); 319 } 320 m_retiredTexturesLock.unlock(); 321 GLUtils::checkGlError("deleteUnusedTextures"); 322 return; 323 } 324 325 // This can be called in the webcore thread in the media player's dtor. 326 void VideoLayerManager::removeLayer(const int layerId) 327 { 328 android::Mutex::Autolock lock(m_videoLayerInfoMapLock); 329 removeLayerInternal(layerId); 330 } 331 332 // This can be called on both UI and webcore thread. Since this is a private 333 // function, it is up to the public function to handle the lock for 334 // m_videoLayerInfoMap. 335 void VideoLayerManager::removeLayerInternal(const int layerId) 336 { 337 // Delete the layerInfo corresponding to this layerId and remove from the map. 338 if (m_videoLayerInfoMap.contains(layerId)) { 339 GLuint textureId = m_videoLayerInfoMap.get(layerId)->textureId; 340 if (textureId) { 341 // Buffer up the retired textures in either UI or webcore thread, 342 // will be purged at deleteUnusedTextures in the UI thread. 343 m_retiredTexturesLock.lock(); 344 m_retiredTextures.append(textureId); 345 m_retiredTexturesLock.unlock(); 346 } 347 delete m_videoLayerInfoMap.get(layerId); 348 m_videoLayerInfoMap.remove(layerId); 349 } 350 return; 351 } 352 353 double VideoLayerManager::drawIcon(const int layerId, IconType type) 354 { 355 // When ratio 0 is returned, the Icon should not be drawn. 356 double ratio = 0; 357 358 android::Mutex::Autolock lock(m_videoLayerInfoMapLock); 359 if (m_videoLayerInfoMap.contains(layerId)) { 360 VideoLayerInfo* pInfo = m_videoLayerInfoMap.get(layerId); 361 // If this is state switching moment, reset the time and state 362 if ((type == PlayIcon && pInfo->iconState != PlayIconShown) 363 || (type == PauseIcon && pInfo->iconState != PauseIconShown)) { 364 pInfo->lastIconShownTime = WTF::currentTime(); 365 pInfo->iconState = (type == PlayIcon) ? PlayIconShown : PauseIconShown; 366 } 367 368 // After switching the state, we calculate the ratio depending on the 369 // time interval. 370 if ((type == PlayIcon && pInfo->iconState == PlayIconShown) 371 || (type == PauseIcon && pInfo->iconState == PauseIconShown)) { 372 double delta = WTF::currentTime() - pInfo->lastIconShownTime; 373 ratio = 1.0 - (delta / PLAY_PAUSE_ICON_SHOW_TIME); 374 } 375 } 376 377 if (ratio > 1 || ratio < 0) 378 ratio = 0; 379 return ratio; 380 } 381 382 } 383 #endif // USE(ACCELERATED_COMPOSITING) 384