Home | History | Annotate | Download | only in layers
      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