• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
VideoLayerManager()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 
getButtonSize()71 int VideoLayerManager::getButtonSize()
72 {
73     return VIDEO_BUTTON_SIZE;
74 }
75 
createTextureFromImage(RenderSkinMediaButton::MediaButton buttonType)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.
initGLResourcesIfNeeded()98 void VideoLayerManager::initGLResourcesIfNeeded()
99 {
100     if (!m_createdTexture) {
101         ALOGD("Reinit GLResource for VideoLayer");
102         initGLResources();
103     }
104 }
105 
initGLResources()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 
cleanupGLResources()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.
getTextureId(const int layerId)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.
getAspectRatio(const int layerId)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.
getMatrix(const int layerId)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 
getTotalMemUsage()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.
registerTexture(const int layerId,const GLuint textureId)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.
updateVideoLayerSize(const int layerId,const int size,const float ratio)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.
updateMatrix(const int layerId,const GLfloat * matrix)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.
recycleTextureMem()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.
deleteUnusedTextures()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.
removeLayer(const int layerId)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.
removeLayerInternal(const int layerId)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 
drawIcon(const int layerId,IconType type)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