2015년 4월 23일 목요일

안드로이드 SurfaceView 에 ffmpeg 디코딩 영상 복사하기

1. JNI 를 통해 SurfaceView 의 Surface 객체 받기

public class DXMediaPlayer extends SurfaceView implements SurfaceHolder.Callback {
    ...
    private native void setSurface(int idx, Surface surface);
    ...

@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i(LOG_TAG(), "surface created");
setSurface(mIdx, holder.getSurface());
}
}

ANativeWindow* m_pNativeWindow;
...
JNIEXPORT void JNICALL Java_com_dxmediaplayer_DXMediaPlayer_setSurface(JNIEnv *pEnv, jobject pObj, jint idx, jobject pSurface)
{
if (pSurface != NULL) {
if (!m_pNativeWindow)
m_pNativeWindow = ANativeWindow_fromSurface(pEnv, pSurface);
} else {
if (m_pNativeWindow)
ANativeWindow_release(m_pNativeWindow);
m_pNativeWindow = NULL;
m_nWindowWidth = m_nWindowHeight = 0;
}
}


2. ffmpeg 디코딩 후 Surface 에 복사

ffmpeg 디코딩후 YUV420 -> RGBA 변환(스케일러 사용)후 그대로 복사해주면 된다. 이때 Surface 사이즈에 맞게 스케일링을 할 필요가 없다 (안드로이드에서 스케일링을 해준다)
즉, ffmpeg 스케일러의 sws_getContext 함수에서 소스 사이즈와 데스티네이션 사이즈를 똑같이 AVFrame의 width/height로 해준다(픽셀포멧만 변환)
그리고 아래와같이 Surface에 복사

 ANativeWindow_Buffer windowBuffer;  
   
 if (ANativeWindow_lock(m_pWindow, &windowBuffer, NULL) < 0) {  
    DXPRINTF("ANativeWindow_lock failed\n");  
 } else {  
    if (windowBuffer.width == windowBuffer.stride) {  
       memcpy(windowBuffer.bits, pFrame->data, pFrame->size);  
    } else {  
       int bpp = GetBitPerPixel(pFrame->pix_fmt)>>3;  
       int stride = windowBuffer.stride * bpp;  
       uint8_t *ptr = pFrame->data;  
       uint8_t *dst = (uint8_t*)windowBuffer.bits;  
       for (int h=0; h<pFrame->dst_height; h++) {  
           memcpy(&dst[h*stride], ptr, stride);  
           ptr += pFrame->dst_width * bpp;  
       }  
    }  
 }  
    
 ANativeWindow_unlockAndPost(m_pWindow);  

* opengl es (GLSurfaceView) 를 사용할 필요가 없다.