1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #define LOG_TAG "MediaTexture" 18 #define LOG_NDEBUG 1 19 20 #include "config.h" 21 #include "MediaTexture.h" 22 23 #include "AndroidLog.h" 24 #include "DrawQuadData.h" 25 #include "TilesManager.h" 26 #include "GLUtils.h" 27 #include "MediaListener.h" 28 29 #if USE(ACCELERATED_COMPOSITING) 30 31 #include <android/native_window.h> 32 #include <gui/SurfaceTexture.h> 33 #include <gui/SurfaceTextureClient.h> 34 #include <JNIUtility.h> 35 #include "WebCoreJni.h" 36 37 // Limits the number of ANativeWindows that can be allocated for video playback. 38 // The limit is currently set to 2 as that is the current max number of 39 // simultaneous HW decodes that our OMX implementation allows. This forces the 40 // media producer to use their own SW decoders for subsequent video streams. 41 #define MAX_WINDOW_COUNT 2 42 43 namespace WebCore { 44 45 MediaTexture::MediaTexture(jobject webViewRef, jobject webViewCoreRef) : android::LightRefBase<MediaTexture>() 46 { 47 JNIEnv* env = JSC::Bindings::getJNIEnv(); 48 m_weakWebViewRef = env->NewWeakGlobalRef(webViewRef); 49 m_weakWebViewCoreRef = env->NewWeakGlobalRef(webViewCoreRef); 50 51 m_contentTexture = 0; 52 m_isContentInverted = false; 53 m_newWindowRequest = false; 54 } 55 56 MediaTexture::~MediaTexture() 57 { 58 if (m_contentTexture) 59 deleteTexture(m_contentTexture, true); 60 for (unsigned int i = 0; i < m_videoTextures.size(); i++) { 61 deleteTexture(m_videoTextures[i], true); 62 } 63 64 JNIEnv* env = JSC::Bindings::getJNIEnv(); 65 env->DeleteWeakGlobalRef(m_weakWebViewRef); 66 env->DeleteWeakGlobalRef(m_weakWebViewCoreRef); 67 } 68 69 bool MediaTexture::isContentInverted() 70 { 71 android::Mutex::Autolock lock(m_mediaLock); 72 return m_isContentInverted; 73 } 74 void MediaTexture::invertContents(bool invertContent) 75 { 76 android::Mutex::Autolock lock(m_mediaLock); 77 m_isContentInverted = invertContent; 78 } 79 80 void MediaTexture::initNativeWindowIfNeeded() 81 { 82 { 83 android::Mutex::Autolock lock(m_mediaLock); 84 85 // check to see if there are any unused textures to delete 86 if (m_unusedTextures.size() != 0) { 87 for (unsigned int i = 0; i < m_unusedTextures.size(); i++) { 88 glDeleteTextures(1, &m_unusedTextures[i]); 89 } 90 m_unusedTextures.clear(); 91 } 92 93 // create a content texture if none exists 94 if (!m_contentTexture) { 95 m_contentTexture = createTexture(); 96 97 // send a message to the WebKit thread to notify the plugin that it can draw 98 if (m_weakWebViewCoreRef) { 99 JNIEnv* env = JSC::Bindings::getJNIEnv(); 100 jobject localWebViewCoreRef = env->NewLocalRef(m_weakWebViewCoreRef); 101 if (localWebViewCoreRef) { 102 jclass wvClass = env->GetObjectClass(localWebViewCoreRef); 103 jmethodID sendPluginDrawMsg = 104 env->GetMethodID(wvClass, "sendPluginDrawMsg", "()V"); 105 env->CallVoidMethod(localWebViewCoreRef, sendPluginDrawMsg); 106 env->DeleteLocalRef(wvClass); 107 env->DeleteLocalRef(localWebViewCoreRef); 108 } 109 checkException(env); 110 } 111 } 112 113 // finally create a video texture if needed 114 if (!m_newWindowRequest) 115 return; 116 117 // add the texture and add it to the list 118 TextureWrapper* videoTexture = createTexture(); 119 m_videoTextures.append(videoTexture); 120 121 // setup the state variables to signal the other thread 122 m_newWindowRequest = false; 123 m_newWindow = videoTexture->nativeWindow; 124 } 125 126 // signal the WebKit thread in case it is waiting 127 m_newMediaRequestCond.signal(); 128 } 129 130 void MediaTexture::draw(const TransformationMatrix& contentMatrix, 131 const TransformationMatrix& videoMatrix, 132 const SkRect& mediaBounds) 133 { 134 android::Mutex::Autolock lock(m_mediaLock); 135 136 if (mediaBounds.isEmpty()) 137 return; 138 139 // draw all the video textures first 140 for (unsigned int i = 0; i < m_videoTextures.size(); i++) { 141 142 TextureWrapper* video = m_videoTextures[i]; 143 144 if (!video->surfaceTexture.get() || video->dimensions.isEmpty() 145 || !video->mediaListener->isFrameAvailable()) 146 continue; 147 148 video->surfaceTexture->updateTexImage(); 149 150 float surfaceMatrix[16]; 151 video->surfaceTexture->getTransformMatrix(surfaceMatrix); 152 153 SkRect dimensions = video->dimensions; 154 dimensions.offset(mediaBounds.fLeft, mediaBounds.fTop); 155 156 #ifdef DEBUG 157 if (!mediaBounds.contains(dimensions)) { 158 ALOGV("The video exceeds is parent's bounds."); 159 } 160 #endif // DEBUG 161 162 TilesManager::instance()->shader()->drawVideoLayerQuad(videoMatrix, 163 surfaceMatrix, dimensions, video->textureId); 164 } 165 166 if (!m_contentTexture->mediaListener->isFrameAvailable()) 167 return; 168 169 m_contentTexture->surfaceTexture->updateTexImage(); 170 171 sp<GraphicBuffer> buf = m_contentTexture->surfaceTexture->getCurrentBuffer(); 172 173 PixelFormat f = buf->getPixelFormat(); 174 // only attempt to use alpha blending if alpha channel exists 175 bool forceAlphaBlending = !( 176 PIXEL_FORMAT_RGBX_8888 == f || 177 PIXEL_FORMAT_RGB_888 == f || 178 PIXEL_FORMAT_RGB_565 == f); 179 180 TextureQuadData data(m_contentTexture->textureId, GL_TEXTURE_EXTERNAL_OES, 181 GL_LINEAR, LayerQuad, &contentMatrix, &mediaBounds, 182 1.0f, forceAlphaBlending); 183 TilesManager::instance()->shader()->drawQuad(&data); 184 } 185 186 ANativeWindow* MediaTexture::requestNativeWindowForVideo() 187 { 188 android::Mutex::Autolock lock(m_mediaLock); 189 190 // the window was not ready before the timeout so return it this time 191 if (ANativeWindow* window = m_newWindow.get()) { 192 m_newWindow.clear(); 193 return window; 194 } 195 196 // we only allow for so many textures, so return NULL if we exceed that limit 197 else if (m_videoTextures.size() >= MAX_WINDOW_COUNT) { 198 return 0; 199 } 200 201 m_newWindowRequest = true; 202 203 // post an inval message to the UI thread to fulfill the request 204 if (m_weakWebViewRef) { 205 JNIEnv* env = JSC::Bindings::getJNIEnv(); 206 jobject localWebViewRef = env->NewLocalRef(m_weakWebViewRef); 207 if (localWebViewRef) { 208 jclass wvClass = env->GetObjectClass(localWebViewRef); 209 jmethodID postInvalMethod = env->GetMethodID(wvClass, "postInvalidate", "()V"); 210 env->CallVoidMethod(localWebViewRef, postInvalMethod); 211 env->DeleteLocalRef(wvClass); 212 env->DeleteLocalRef(localWebViewRef); 213 } 214 checkException(env); 215 } 216 217 //block until the request can be fulfilled or we time out 218 bool timedOut = false; 219 while (m_newWindowRequest && !timedOut) { 220 int ret = m_newMediaRequestCond.waitRelative(m_mediaLock, 500000000); // .5 sec 221 timedOut = ret == TIMED_OUT; 222 } 223 224 // if the window is ready then return it otherwise return NULL 225 if (ANativeWindow* window = m_newWindow.get()) { 226 m_newWindow.clear(); 227 return window; 228 } 229 return 0; 230 } 231 232 ANativeWindow* MediaTexture::getNativeWindowForContent() 233 { 234 android::Mutex::Autolock lock(m_mediaLock); 235 if (m_contentTexture) 236 return m_contentTexture->nativeWindow.get(); 237 else 238 return 0; 239 } 240 241 void MediaTexture::releaseNativeWindow(const ANativeWindow* window) 242 { 243 android::Mutex::Autolock lock(m_mediaLock); 244 for (unsigned int i = 0; i < m_videoTextures.size(); i++) { 245 if (m_videoTextures[i]->nativeWindow.get() == window) { 246 deleteTexture(m_videoTextures[i]); 247 m_videoTextures.remove(i); 248 break; 249 } 250 } 251 } 252 253 void MediaTexture::setDimensions(const ANativeWindow* window, 254 const SkRect& dimensions) 255 { 256 android::Mutex::Autolock lock(m_mediaLock); 257 for (unsigned int i = 0; i < m_videoTextures.size(); i++) { 258 if (m_videoTextures[i]->nativeWindow.get() == window) { 259 m_videoTextures[i]->dimensions = dimensions; 260 break; 261 } 262 } 263 } 264 265 void MediaTexture::setFramerateCallback(const ANativeWindow* window, 266 FramerateCallbackProc callback) 267 { 268 android::Mutex::Autolock lock(m_mediaLock); 269 for (unsigned int i = 0; i < m_videoTextures.size(); i++) { 270 if (m_videoTextures[i]->nativeWindow.get() == window) { 271 m_videoTextures[i]->mediaListener->setFramerateCallback(callback); 272 break; 273 } 274 } 275 } 276 277 MediaTexture::TextureWrapper* MediaTexture::createTexture() 278 { 279 TextureWrapper* wrapper = new TextureWrapper(); 280 281 // populate the wrapper 282 glGenTextures(1, &wrapper->textureId); 283 wrapper->surfaceTexture = new android::SurfaceTexture(wrapper->textureId); 284 wrapper->nativeWindow = new android::SurfaceTextureClient(wrapper->surfaceTexture); 285 wrapper->dimensions.setEmpty(); 286 287 // setup callback 288 wrapper->mediaListener = new MediaListener(m_weakWebViewRef, 289 wrapper->surfaceTexture, 290 wrapper->nativeWindow); 291 wrapper->surfaceTexture->setFrameAvailableListener(wrapper->mediaListener); 292 293 return wrapper; 294 } 295 296 void MediaTexture::deleteTexture(TextureWrapper* texture, bool force) 297 { 298 if (texture->surfaceTexture.get()) 299 texture->surfaceTexture->setFrameAvailableListener(0); 300 301 if (force) 302 glDeleteTextures(1, &texture->textureId); 303 else 304 m_unusedTextures.append(texture->textureId); 305 306 // clear the strong pointer references 307 texture->mediaListener.clear(); 308 texture->nativeWindow.clear(); 309 texture->surfaceTexture.clear(); 310 311 delete texture; 312 } 313 314 } // namespace WebCore 315 316 #endif // USE(ACCELERATED_COMPOSITING) 317