GLSurfaceView не возобновляет поток Open GL при вызове onResume

Моя проблема заключается в следующем: на «старых» устройствах Android (v 2.2 и 2.3) после поворота мой GLSurfaceView пуст. Я мог видеть эти звонки в моем журнале:

- rotation detected! -
CTestApp(10669): entering onConfigurationChanged method.
MainActivity(10669): entering onPause method.
*WEBRTC*(10669): ViEAndroidGLES20::onPause
*WEBRTC*(10669): ContextFactory::destroyContext
*WEBRTC*(10669): ViEAndroidGLES20::onPause
*WEBRTC*(10669): ContextFactory::destroyContext
MainActivity(10669): end of onPause method.
MainActivity(10669): entering onStop method.
*WEBRTC*(10669): ViEAndroidGLES20::onDetachedFromWindow
*WEBRTC*(10669): ViEAndroidGLES20::onDetachedFromWindow
MainActivity(10669): end of onStop method.
MainActivity(10669): entering onDestroy method.
MainActivity(10669): end of onDestroy method.
MainActivity(10669): entering onCreate method.
MainActivity(10669): entering onStart method.
MainActivity(10669): end of onStart method.
MainActivity(10669): entering onResume method.
*WEBRTC*(10669): ViEAndroidGLES20::onResume
*WEBRTC*(10669): ViEAndroidGLES20::onResume
MainActivity(10669): end of onResume method.
*WEBRTC*(10669): ViEAndroidGLES20::onAttachedToWindow
*WEBRTC*(10669): ViEAndroidGLES20::onAttachedToWindow

На более новых устройствах Android рендеринг видеопотоков корректно возобновляется после поворота устройства:

Журнал работающего устройства аналогичен предыдущему (нерабочему) логу, за исключением того, что эти трассировки появляются после вызовов onAttachedToWindow:

creating OpenGL ES 2.0 context
ViEAndroidGLES20::onSurfaceCreated

В отладчике Eclipse я заметил, что 2 потока OpenGl, которые были приостановлены во время уничтожения активности, не возобновляются. Похоже, что существует разница в поведении GLSurfaceView между Android 2.3 и 4.0, из-за которой поток OpenGl возобновляется только в более новой версии. Кто-нибудь знает об этом?

Вот детали устройств, которые я использовал для своих тестов:

рабочие устройства:

  • Galaxy Nexus под управлением Android версии 4.1.1
  • Galaxy Tab 10.1 под управлением Android версии 4.0.4

"плохие" устройства: - HTC Desire, под управлением Android 2.3.5 - Motorola droid, под управлением Android 2.2

Вот дополнительная информация о коде, который я использовал.

У меня есть следующий класс, расширяющий GLSurfaceView:

public class ViEAndroidGLES20 extends GLSurfaceView
    implements GLSurfaceView.Renderer {
    private static String TAG = "WEBRTC-JR";
    private static final boolean DEBUG = true;
  // True if onSurfaceCreated has been called.
  private boolean surfaceCreated = false;
  private boolean openGLCreated = false;
  // True if NativeFunctionsRegistered has been called.
  private boolean nativeFunctionsRegisted = false;
  private ReentrantLock nativeFunctionLock = new ReentrantLock();
  // Address of Native object that will do the drawing.
  private long nativeObject = 0;
  private int viewWidth = 0;
  private int viewHeight = 0;

  public static boolean UseOpenGL2(Object renderWindow) {
    return ViEAndroidGLES20.class.isInstance(renderWindow);
  }

  public ViEAndroidGLES20(Context context) {
    super(context);
        init(false, 0, 0);
    }

    public ViEAndroidGLES20(Context context, boolean translucent,
            int depth, int stencil) {
        super(context);
        init(translucent, depth, stencil);
    }

    private void init(boolean translucent, int depth, int stencil) {

        // By default, GLSurfaceView() creates a RGB_565 opaque surface.
        // If we want a translucent one, we should change the surface's
        // format here, using PixelFormat.TRANSLUCENT for GL Surfaces
        // is interpreted as any 32-bit surface with alpha by SurfaceFlinger.
        if (translucent) {
            this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
        }

    // Setup the context factory for 2.0 rendering.
    // See ContextFactory class definition below
    setEGLContextFactory(new ContextFactory());

        // We need to choose an EGLConfig that matches the format of
        // our surface exactly. This is going to be done in our
        // custom config chooser. See ConfigChooser class definition
        // below.
        setEGLConfigChooser( translucent ?
                             new ConfigChooser(8, 8, 8, 8, depth, stencil) :
                             new ConfigChooser(5, 6, 5, 0, depth, stencil) );

        // Set the renderer responsible for frame rendering
     this.setRenderer(this);
     this.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
  }

    private static class ContextFactory implements GLSurfaceView.EGLContextFactory {
        private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
            Log.w(TAG, "creating OpenGL ES 2.0 context");
            checkEglError("Before eglCreateContext", egl);
            int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
            EGLContext context = egl.eglCreateContext(display, eglConfig,
                    EGL10.EGL_NO_CONTEXT, attrib_list);
            checkEglError("After eglCreateContext", egl);
            return context;
        }

        public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
            Log.d("*WEBRTC*", "ContextFactory::destroyContext");
            egl.eglDestroyContext(display, context);
        }
    }

  private static void checkEglError(String prompt, EGL10 egl) {
    int error;
    while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
      Log.e("*WEBRTC*", String.format("%s: EGL error: 0x%x", prompt, error));
    }
  }

    private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {

    public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
      mRedSize = r;
      mGreenSize = g;
      mBlueSize = b;
      mAlphaSize = a;
      mDepthSize = depth;
      mStencilSize = stencil;
    }

    // This EGL config specification is used to specify 2.0 rendering.
    // We use a minimum size of 4 bits for red/green/blue, but will
    // perform actual matching in chooseConfig() below.
    private static int EGL_OPENGL_ES2_BIT = 4;
    private static int[] s_configAttribs2 =
    {
      EGL10.EGL_RED_SIZE, 4,
      EGL10.EGL_GREEN_SIZE, 4,
      EGL10.EGL_BLUE_SIZE, 4,
      EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
      EGL10.EGL_NONE
    };

    public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {

      // Get the number of minimally matching EGL configurations
      int[] num_config = new int[1];
      egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config);

      int numConfigs = num_config[0];

      if (numConfigs <= 0) {
        throw new IllegalArgumentException("No configs match configSpec");
      }

      // Allocate then read the array of minimally matching EGL configs
      EGLConfig[] configs = new EGLConfig[numConfigs];
      egl.eglChooseConfig(display, s_configAttribs2, configs,                          numConfigs, num_config);

      // Now return the "best" one
      return chooseConfig(egl, display, configs);
    }

    public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
                                  EGLConfig[] configs) {
      for(EGLConfig config : configs) {
        int d = findConfigAttrib(egl, display, config,
                                 EGL10.EGL_DEPTH_SIZE, 0);
        int s = findConfigAttrib(egl, display, config,
                                 EGL10.EGL_STENCIL_SIZE, 0);

        // We need at least mDepthSize and mStencilSize bits
        if (d < mDepthSize || s < mStencilSize)
          continue;

        // We want an *exact* match for red/green/blue/alpha
        int r = findConfigAttrib(egl, display, config,
                                 EGL10.EGL_RED_SIZE, 0);
        int g = findConfigAttrib(egl, display, config,
                                 EGL10.EGL_GREEN_SIZE, 0);
        int b = findConfigAttrib(egl, display, config,
                                 EGL10.EGL_BLUE_SIZE, 0);
        int a = findConfigAttrib(egl, display, config,
                                 EGL10.EGL_ALPHA_SIZE, 0);

        if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize)
          return config;
      }
      return null;
    }

    private int findConfigAttrib(EGL10 egl, EGLDisplay display,
                                 EGLConfig config, int attribute, int defaultValue) {

      if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
        return mValue[0];
      }
      return defaultValue;
    }

    // Subclasses can adjust these values:
    protected int mRedSize;
    protected int mGreenSize;
    protected int mBlueSize;
    protected int mAlphaSize;
    protected int mDepthSize;
    protected int mStencilSize;
    private int[] mValue = new int[1];
  }

  // IsSupported
  // Return true if this device support Open GL ES 2.0 rendering.
  public static boolean IsSupported(Context context) {
    ActivityManager am =
        (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    ConfigurationInfo info = am.getDeviceConfigurationInfo();
    if(info.reqGlEsVersion >= 0x20000) {
      // Open GL ES 2.0 is supported.
      return true;
    }
    return false;
  }

   public void onDrawFrame(GL10 gl) {
    nativeFunctionLock.lock();
    if(!nativeFunctionsRegisted || !surfaceCreated) {
      nativeFunctionLock.unlock();
      return;
    }

    if(!openGLCreated) {
      if(0 != CreateOpenGLNative(nativeObject, viewWidth, viewHeight)) {
        return; // Failed to create OpenGL
      }
      openGLCreated = true; // Created OpenGL successfully
    }
    DrawNative(nativeObject); // Draw the new frame
    nativeFunctionLock.unlock();
  }

   public void onSurfaceChanged(GL10 gl, int width, int height) {

    if (DEBUG)
    {
      Log.d("*WEBRTC*", "ViEAndroidGLES20::onSurfaceChanged");
    }

    surfaceCreated = true;
    viewWidth = width;
    viewHeight = height;

    nativeFunctionLock.lock();
    if(nativeFunctionsRegisted) {
      if(CreateOpenGLNative(nativeObject,width,height) == 0)
      {
        openGLCreated = true;
      }
      else
      {
        Log.e("*WEBRTC*", "ViEAndroidGLES20::onSurfaceChanged - failed to openGlCreated!");
      }
    }
    nativeFunctionLock.unlock();
  }

   public void onSurfaceCreated(GL10 gl, EGLConfig config) {

    if (DEBUG)
    {
      Log.d("*WEBRTC*", "ViEAndroidGLES20::onSurfaceCreated");
    }
  }

  public void ReDraw() {
    if(surfaceCreated) {
      // Request the renderer to redraw using the render thread context.
      this.requestRender();
    }
  }

  private native int CreateOpenGLNative(long nativeObject,
                                        int width, int height);
  private native void DrawNative(long nativeObject);

  protected void onAttachedToWindow()
  {
      if (DEBUG)
      {
          Log.d("*WEBRTC*", "ViEAndroidGLES20::onAttachedToWindow");
      }

      super.onAttachedToWindow();
  }

  protected void onDetachedFromWindow()
  {
      if (DEBUG)
      {
          Log.d("*WEBRTC*", "ViEAndroidGLES20::onDetachedFromWindow");
      }

      super.onDetachedFromWindow();
  }

  public void onPause()
  {
      if (DEBUG)
      {
          Log.d("*WEBRTC*", "ViEAndroidGLES20::onPause");
      }

      super.onPause();
  }

  public void onResume()
  {
      if (DEBUG)
      {
          Log.d("*WEBRTC*", "ViEAndroidGLES20::onResume");
      }

      super.onResume();
  }
}

Когда я выполняю поворот устройства, моя основная активность уничтожается, но мое приложение сохраняет ссылку на экземпляры [[ViENandroidGLES20]] (члены класса m_RemoteView1 и m_RemoteView2). Эти ссылки извлекаются в обратном вызове onStart() действия, как показано ниже.

// The activity is about to become visible.
@Override protected void onStart() {

    Log.d("MainActivity", "entering onStart method.");

    super.onStart();

    //   The application is responsible of keeping valid references to the surface view
    //   used to perform local capture and remote stream rendering.
    m_RemoteView1 = ((CTestApp)getApplication()).GetRemoteVideoView();
    m_RemoteView2 = ((CTestApp)getApplication()).GetRemoteVideoView2();

    if (m_RemoteView1 != null)
    {
        LinearLayout layout = (LinearLayout) findViewById(R.id.remoteVideoRenderLayout1);
        layout.addView(m_RemoteView1);
    }

    if (m_RemoteView2 != null)
    {
        LinearLayout layout = (LinearLayout) findViewById(R.id.remoteVideoRenderLayout2);
        layout.addView(m_RemoteView2);
    }
}

// The activity has become visible, it is now resumed.
@Override protected void onResume() {

    Log.d("MainActivity", "entering onResume method.");

    super.onResume();

    // A GLSurfaceView must be notified when the activity is paused and resumed.  GLSurfaceView clients
    // are required to call onPause() when the activity pauses and onResume() when the activity resumes.
    ((GLSurfaceView)m_RemoteView1).onResume();
    ((GLSurfaceView)m_RemoteView2).onResume();
}

Обратите внимание, что я также включил реализацию обратного вызова onResume() моей основной активности, чтобы показать, что я вызываю GLSurfaceView.onResume() при возобновлении активности.


person user2005447    schedule 25.01.2013    source источник
comment
stackoverflow.com/questions/11381543/ stackoverflow.com /вопросы/11802885/   -  person Patt Mehta    schedule 27.01.2013


Ответы (1)


Наконец-то мне удалось найти проблему с моей проблемой. Проблема возникла из-за разницы в поведении класса android.opengl.GLSurfaceView между Android 2.3 и 4.x, которая вызвала проблему. В реализации GLSurfaceView для Android 4.x обратный вызов onAttachedToWindow вызывал перезапуск связанного GLThread.

Этот перезапуск GLThread отсутствует в реализациях Android 2.2 и 2.3. Отсутствие возобновления потока OpenGL приводило к тому, что представления рендеринга становились пустыми после выполнения вызовов ViewGroup::removeView/addView, как в сценарии ротации.

Чтобы исправить эту проблему, я добавил в свой проект класс newGLSurfaceView, который является копией класса GLSurfaceView.java исходного кода Android 4.1.

Спасибо,

person user2005447    schedule 06.02.2013
comment
Как вы заставили его скомпилировать? Вам также пришлось копировать все зависимости? - person Matt Wolfe; 23.07.2014