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