diff options
author | davilla <davilla@4pi.com> | 2013-08-09 09:56:45 -0400 |
---|---|---|
committer | davilla <davilla@4pi.com> | 2013-10-02 11:39:49 -0400 |
commit | 93ef38aa04a0e6dea6dbeac65ab6590524cc044e (patch) | |
tree | 213cc7b3c545e7f25dce0900cbd110e00c15aeb4 | |
parent | e70222d518b65ad59e1d59a8c3b33992067bb5b3 (diff) |
droid: Add Android MediaCodec for DVDPlayer
21 files changed, 1464 insertions, 16 deletions
diff --git a/language/English/strings.po b/language/English/strings.po index 0b6221a776..24c40852a6 100755 --- a/language/English/strings.po +++ b/language/English/strings.po @@ -5801,7 +5801,11 @@ msgctxt "#13438" msgid "Allow hardware acceleration (amcodec)" msgstr "" -#empty strings from id 13439 to 13499 +msgctxt "#13439" +msgid "Allow hardware acceleration (MediaCodec)" +msgstr "" + +#empty strings from id 13440 to 13499 #: system/settings/settings.xml msgctxt "#13500" @@ -14366,6 +14370,11 @@ msgctxt "#36543" msgid "Enable this to make dialogue louder compared to background sounds when downmixing multichannel audio" msgstr "" +#: system/settings/settings.xml +msgctxt "#36544" +msgid "Enable hardware decoding of video files." +msgstr "" + #reserved strings 365XX #: xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamNavigator.cpp diff --git a/system/settings/android.xml b/system/settings/android.xml index 756d223f94..ce46a88af4 100644 --- a/system/settings/android.xml +++ b/system/settings/android.xml @@ -27,4 +27,15 @@ </group> </category> </section> + <section id="videos"> + <category id="videoplayer"> + <group id="2"> + <setting id="videoplayer.usemediacodec" type="boolean" label="13439" help="36544"> + <visible>HAS_MEDIACODEC</visible> + <level>2</level> + <default>true</default> + </setting> + </group> + </category> + </section> </settings> diff --git a/system/shaders/guishader_frag_rgba_oes.glsl b/system/shaders/guishader_frag_rgba_oes.glsl new file mode 100644 index 0000000000..828d48bef3 --- /dev/null +++ b/system/shaders/guishader_frag_rgba_oes.glsl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#extension GL_OES_EGL_image_external : require + +precision mediump float; +uniform samplerExternalOES m_samp0; +varying vec4 m_cord0; + +// SM_TEXTURE_OES +void main () +{ + gl_FragColor.rgba = texture2D(m_samp0, m_cord0.xy).rgba; +} diff --git a/system/shaders/guishader_vert.glsl b/system/shaders/guishader_vert.glsl index 65ca101987..46f3399521 100644 --- a/system/shaders/guishader_vert.glsl +++ b/system/shaders/guishader_vert.glsl @@ -27,12 +27,13 @@ varying vec4 m_cord1; varying lowp vec4 m_colour; uniform mat4 m_proj; uniform mat4 m_model; +uniform mat4 m_coord0Matrix; void main () { mat4 mvp = m_proj * m_model; gl_Position = mvp * m_attrpos; m_colour = m_attrcol; - m_cord0 = m_attrcord0; + m_cord0 = m_coord0Matrix * m_attrcord0; m_cord1 = m_attrcord1; } diff --git a/xbmc/android/jni/MediaCodecBufferInfo.cpp b/xbmc/android/jni/MediaCodecBufferInfo.cpp index 80d99e16f9..e966e7363b 100644 --- a/xbmc/android/jni/MediaCodecBufferInfo.cpp +++ b/xbmc/android/jni/MediaCodecBufferInfo.cpp @@ -29,7 +29,7 @@ using namespace jni; CJNIMediaCodecBufferInfo::CJNIMediaCodecBufferInfo() : CJNIBase("android/media/MediaCodec$BufferInfo") { m_object = new_object(GetClassName(), "<init>", "()V"); - //m_object.setGlobal(); + m_object.setGlobal(); } void CJNIMediaCodecBufferInfo::set(int newOffset, int newSize, int64_t newTimeUs, int newFlags) diff --git a/xbmc/android/jni/SurfaceTexture.cpp b/xbmc/android/jni/SurfaceTexture.cpp index 63ca3821d0..df8d5daa90 100644 --- a/xbmc/android/jni/SurfaceTexture.cpp +++ b/xbmc/android/jni/SurfaceTexture.cpp @@ -25,6 +25,8 @@ #include "jutils/jutils-details.hpp" +#include <algorithm> + using namespace jni; ////////////////////////////////////////////////////////////////////////////////// @@ -40,11 +42,7 @@ CJNISurfaceTextureOnFrameAvailableListener::CJNISurfaceTextureOnFrameAvailableLi // Convert "the/class/name" to "the.class.name" as loadClass() expects it. std::string dotClassName = GetClassName(); - for (std::string::iterator it = dotClassName.begin(); it != dotClassName.end(); ++it) - { - if (*it == '/') - *it = '.'; - } + std::replace(dotClassName.begin(), dotClassName.end(), '/', '.'); m_object = new_object(appInstance->getClassLoader().loadClass(dotClassName)); m_object.setGlobal(); diff --git a/xbmc/cores/VideoRenderers/LinuxRendererGLES.cpp b/xbmc/cores/VideoRenderers/LinuxRendererGLES.cpp index ee437a8996..4727ccb543 100644 --- a/xbmc/cores/VideoRenderers/LinuxRendererGLES.cpp +++ b/xbmc/cores/VideoRenderers/LinuxRendererGLES.cpp @@ -75,6 +75,10 @@ static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; #endif +#if defined(TARGET_ANDROID) +#include "DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h" +#endif + using namespace Shaders; CLinuxRendererGLES::YUVBUFFER::YUVBUFFER() @@ -92,6 +96,9 @@ CLinuxRendererGLES::YUVBUFFER::YUVBUFFER() stf = NULL; eglimg = EGL_NO_IMAGE_KHR; #endif +#if defined(TARGET_ANDROID) + mediacodec = NULL; +#endif } CLinuxRendererGLES::YUVBUFFER::~YUVBUFFER() @@ -265,7 +272,13 @@ int CLinuxRendererGLES::GetImage(YV12Image *image, int source, bool readonly) { return source; } + #endif + if ( m_renderMethod & RENDER_MEDIACODEC ) + { + return source; + } + #ifdef HAVE_VIDEOTOOLBOXDECODER if (m_renderMethod & RENDER_CVREF ) { @@ -484,7 +497,7 @@ void CLinuxRendererGLES::RenderUpdate(bool clear, DWORD flags, DWORD alpha) int index = m_iYV12RenderBuffer; YUVBUFFER& buf = m_buffers[index]; - if (m_format != RENDER_FMT_OMXEGL && m_format != RENDER_FMT_EGLIMG) + if (m_format != RENDER_FMT_OMXEGL && m_format != RENDER_FMT_EGLIMG && m_format != RENDER_FMT_MEDIACODEC) { if (!buf.fields[FIELD_FULL][0].id) return; } @@ -565,6 +578,9 @@ unsigned int CLinuxRendererGLES::PreInit() #ifdef HAS_LIBSTAGEFRIGHT m_formats.push_back(RENDER_FMT_EGLIMG); #endif +#if defined(TARGET_ANDROID) + m_formats.push_back(RENDER_FMT_MEDIACODEC); +#endif // setup the background colour m_clearColour = (float)(g_advancedSettings.m_videoBlackBarColour & 0xff) / 0xff; @@ -670,6 +686,12 @@ void CLinuxRendererGLES::LoadShaders(int field) m_renderMethod = RENDER_EGLIMG; break; } + else if (m_format == RENDER_FMT_MEDIACODEC) + { + CLog::Log(LOGNOTICE, "GL: Using MediaCodec render method"); + m_renderMethod = RENDER_MEDIACODEC; + break; + } else if (m_format == RENDER_FMT_BYPASS) { CLog::Log(LOGNOTICE, "GL: Using BYPASS render method"); @@ -750,6 +772,13 @@ void CLinuxRendererGLES::LoadShaders(int field) m_textureCreate = &CLinuxRendererGLES::CreateEGLIMGTexture; m_textureDelete = &CLinuxRendererGLES::DeleteEGLIMGTexture; } + else if (m_format == RENDER_FMT_MEDIACODEC) + { + m_textureUpload = &CLinuxRendererGLES::UploadSurfaceTexture; + m_textureCreate = &CLinuxRendererGLES::CreateSurfaceTexture; + m_textureDelete = &CLinuxRendererGLES::DeleteSurfaceTexture; + } + else { // default to YV12 texture handlers @@ -826,6 +855,11 @@ void CLinuxRendererGLES::ReleaseBuffer(int idx) CVBufferRelease(buf.cvBufferRef); buf.cvBufferRef = NULL; #endif +#if defined(TARGET_ANDROID) + YUVBUFFER &buf = m_buffers[idx]; + + SAFE_RELEASE(m_buffers[idx].mediacodec); +#endif } void CLinuxRendererGLES::Render(DWORD flags, int index) @@ -883,6 +917,10 @@ void CLinuxRendererGLES::Render(DWORD flags, int index) RenderCoreVideoRef(index, m_currentField); VerifyGLState(); } + else if (m_renderMethod & RENDER_MEDIACODEC) + { + RenderMediaCodec(index, m_currentField); + } else { RenderSoftware(index, m_currentField); @@ -1372,6 +1410,84 @@ void CLinuxRendererGLES::RenderEglImage(int index, int field) #endif } +void CLinuxRendererGLES::RenderMediaCodec(int index, int field) +{ +#if defined(TARGET_ANDROID) + #ifdef DEBUG_VERBOSE + unsigned int time = XbmcThreads::SystemClockMillis(); + #endif + + YUVPLANE &plane = m_buffers[index].fields[0][0]; + + glDisable(GL_DEPTH_TEST); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, plane.id); + + g_Windowing.EnableGUIShader(SM_TEXTURE_RGBA_OES); + + glUniformMatrix4fv(g_Windowing.GUIShaderGetCoord0Matrix(), 1, GL_FALSE, m_textureMatrix); + + GLubyte idx[4] = {0, 1, 3, 2}; //determines order of triangle strip + GLfloat ver[4][4]; + GLfloat tex[4][4]; + + GLint posLoc = g_Windowing.GUIShaderGetPos(); + GLint texLoc = g_Windowing.GUIShaderGetCoord0(); + + + glVertexAttribPointer(posLoc, 4, GL_FLOAT, 0, 0, ver); + glVertexAttribPointer(texLoc, 4, GL_FLOAT, 0, 0, tex); + + glEnableVertexAttribArray(posLoc); + glEnableVertexAttribArray(texLoc); + + // Set vertex coordinates + for(int i = 0; i < 4; i++) + { + ver[i][0] = m_rotatedDestCoords[i].x; + ver[i][1] = m_rotatedDestCoords[i].y; + ver[i][2] = 0.0f; // set z to 0 + ver[i][3] = 1.0f; + } + + // Set texture coordinates (MediaCodec is flipped in y) + tex[0][0] = tex[3][0] = 0.0f; + tex[0][1] = tex[1][1] = 1.0f; + tex[1][0] = tex[2][0] = 1.0f; + tex[2][1] = tex[3][1] = 0.0f; + + for(int i = 0; i < 4; i++) + { + tex[i][2] = 0.0f; + tex[i][3] = 1.0f; + } + + glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx); + + glDisableVertexAttribArray(posLoc); + glDisableVertexAttribArray(texLoc); + + const float identity[16] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + glUniformMatrix4fv(g_Windowing.GUIShaderGetCoord0Matrix(), 1, GL_FALSE, identity); + + g_Windowing.DisableGUIShader(); + VerifyGLState(); + + glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); + VerifyGLState(); + + #ifdef DEBUG_VERBOSE + CLog::Log(LOGDEBUG, "RenderMediaCodecImage %d: tm:%d", index, XbmcThreads::SystemClockMillis() - time); + #endif +#endif +} + void CLinuxRendererGLES::RenderCoreVideoRef(int index, int field) { #ifdef HAVE_VIDEOTOOLBOXDECODER @@ -2006,6 +2122,34 @@ bool CLinuxRendererGLES::CreateEGLIMGTexture(int index) return true; } +//******************************************************************************************************** +// SurfaceTexture creation, deletion, copying + clearing +//******************************************************************************************************** +void CLinuxRendererGLES::UploadSurfaceTexture(int index) +{ +#if defined(TARGET_ANDROID) + if (m_buffers[index].mediacodec) + { + m_buffers[index].fields[0][0].id = m_buffers[index].mediacodec->GetTextureID(); + m_buffers[index].mediacodec->ReleaseOutputBuffer(true); + m_buffers[index].mediacodec->UpdateTexImage(); + m_buffers[index].mediacodec->GetTransformMatrix(m_textureMatrix); + SAFE_RELEASE(m_buffers[index].mediacodec); + } + +#endif +} +void CLinuxRendererGLES::DeleteSurfaceTexture(int index) +{ +#if defined(TARGET_ANDROID) + SAFE_RELEASE(m_buffers[index].mediacodec); +#endif +} +bool CLinuxRendererGLES::CreateSurfaceTexture(int index) +{ + return true; +} + void CLinuxRendererGLES::SetTextureFilter(GLenum method) { for (int i = 0 ; i<m_NumYV12Buffers ; i++) @@ -2123,6 +2267,9 @@ bool CLinuxRendererGLES::Supports(EINTERLACEMETHOD method) if(m_renderMethod & RENDER_EGLIMG) return false; + if(m_renderMethod & RENDER_MEDIACODEC) + return false; + if(m_renderMethod & RENDER_CVREF) return false; @@ -2188,7 +2335,8 @@ unsigned int CLinuxRendererGLES::GetProcessorSize() { if(m_format == RENDER_FMT_OMXEGL || m_format == RENDER_FMT_CVBREF - || m_format == RENDER_FMT_EGLIMG) + || m_format == RENDER_FMT_EGLIMG + || m_format == RENDER_FMT_MEDIACODEC) return 1; else return 0; @@ -2233,5 +2381,22 @@ void CLinuxRendererGLES::AddProcessor(CStageFrightVideo* stf, EGLImageKHR eglimg } #endif +#if defined(TARGET_ANDROID) +void CLinuxRendererGLES::AddProcessor(CDVDMediaCodecInfo *mediacodec, int index) +{ +#ifdef DEBUG_VERBOSE + unsigned int time = XbmcThreads::SystemClockMillis(); +#endif + + YUVBUFFER &buf = m_buffers[index]; + if (mediacodec) + buf.mediacodec = mediacodec->Retain(); + +#ifdef DEBUG_VERBOSE + CLog::Log(LOGDEBUG, "AddProcessor %d: img:%d: tm:%d\n", index, buf.mediacodec->GetTexture(), XbmcThreads::SystemClockMillis() - time); +#endif +} +#endif + #endif diff --git a/xbmc/cores/VideoRenderers/LinuxRendererGLES.h b/xbmc/cores/VideoRenderers/LinuxRendererGLES.h index e15ec05155..2c72e151ef 100644 --- a/xbmc/cores/VideoRenderers/LinuxRendererGLES.h +++ b/xbmc/cores/VideoRenderers/LinuxRendererGLES.h @@ -41,6 +41,7 @@ namespace Shaders { class BaseYUV2RGBShader; } namespace Shaders { class BaseVideoFilterShader; } class COpenMaxVideo; class CStageFrightVideo; +class CDVDMediaCodecInfo; typedef std::vector<int> Features; @@ -87,7 +88,8 @@ enum RenderMethod RENDER_OMXEGL = 0x040, RENDER_CVREF = 0x080, RENDER_BYPASS = 0x100, - RENDER_EGLIMG = 0x200 + RENDER_EGLIMG = 0x200, + RENDER_MEDIACODEC = 0x400 }; enum RenderQuality @@ -167,6 +169,10 @@ public: #ifdef HAS_LIBSTAGEFRIGHT virtual void AddProcessor(CStageFrightVideo* stf, EGLImageKHR eglimg, int index); #endif +#if defined(TARGET_ANDROID) + // mediaCodec + virtual void AddProcessor(CDVDMediaCodecInfo *mediacodec, int index); +#endif protected: virtual void Render(DWORD flags, int index); @@ -198,6 +204,10 @@ protected: void DeleteEGLIMGTexture(int index); bool CreateEGLIMGTexture(int index); + void UploadSurfaceTexture(int index); + void DeleteSurfaceTexture(int index); + bool CreateSurfaceTexture(int index); + void CalculateTextureSourceRects(int source, int num_planes); // renderers @@ -207,6 +217,7 @@ protected: void RenderOpenMax(int index, int field); // OpenMAX rgb texture void RenderEglImage(int index, int field); // Android OES texture void RenderCoreVideoRef(int index, int field); // CoreVideo reference + void RenderMediaCodec(int index, int field); // MediaCodec reference CFrameBufferObject m_fbo; @@ -266,6 +277,10 @@ protected: CStageFrightVideo* stf; EGLImageKHR eglimg; #endif +#if defined(TARGET_ANDROID) + // mediacodec + CDVDMediaCodecInfo *mediacodec; +#endif }; typedef YUVBUFFER YUVBUFFERS[NUM_BUFFERS]; @@ -296,6 +311,7 @@ protected: struct SwsContext *m_sw_context; BYTE *m_rgbBuffer; // if software scale is used, this will hold the result image unsigned int m_rgbBufferSize; + float m_textureMatrix[16]; }; diff --git a/xbmc/cores/VideoRenderers/RenderFormats.h b/xbmc/cores/VideoRenderers/RenderFormats.h index 3b091947d3..f15e80d892 100644 --- a/xbmc/cores/VideoRenderers/RenderFormats.h +++ b/xbmc/cores/VideoRenderers/RenderFormats.h @@ -36,6 +36,7 @@ enum ERenderFormat { RENDER_FMT_CVBREF, RENDER_FMT_BYPASS, RENDER_FMT_EGLIMG, + RENDER_FMT_MEDIACODEC, }; #endif diff --git a/xbmc/cores/VideoRenderers/RenderManager.cpp b/xbmc/cores/VideoRenderers/RenderManager.cpp index fbc2aaec11..249222a06a 100644 --- a/xbmc/cores/VideoRenderers/RenderManager.cpp +++ b/xbmc/cores/VideoRenderers/RenderManager.cpp @@ -26,6 +26,7 @@ #include "threads/CriticalSection.h" #include "video/VideoReferenceClock.h" #include "utils/MathUtils.h" +#include "threads/Atomics.h" #include "threads/SingleLock.h" #include "utils/log.h" #include "utils/TimeUtils.h" @@ -937,6 +938,10 @@ int CXBMCRenderManager::AddVideoPicture(DVDVideoPicture& pic) else if(pic.format == RENDER_FMT_EGLIMG) m_pRenderer->AddProcessor(pic.stf, pic.eglimg, index); #endif +#if defined(TARGET_ANDROID) + else if(pic.format == RENDER_FMT_MEDIACODEC) + m_pRenderer->AddProcessor(pic.mediacodec, index); +#endif m_pRenderer->ReleaseImage(index, false); diff --git a/xbmc/cores/dvdplayer/DVDCodecs/DVDFactoryCodec.cpp b/xbmc/cores/dvdplayer/DVDCodecs/DVDFactoryCodec.cpp index bc3072a2a0..183e2c5163 100644 --- a/xbmc/cores/dvdplayer/DVDCodecs/DVDFactoryCodec.cpp +++ b/xbmc/cores/dvdplayer/DVDCodecs/DVDFactoryCodec.cpp @@ -43,6 +43,9 @@ #include "utils/AMLUtils.h" #include "Video/DVDVideoCodecAmlogic.h" #endif +#if defined(TARGET_ANDROID) +#include "Video/DVDVideoCodecAndroidMediaCodec.h" +#endif #include "Audio/DVDAudioCodecFFmpeg.h" #include "Audio/DVDAudioCodecLibMad.h" #include "Audio/DVDAudioCodecPcm.h" @@ -159,6 +162,11 @@ CDVDVideoCodec* CDVDFactoryCodec::CreateVideoCodec(CDVDStreamInfo &hint, unsigne #else hwSupport += "AMCodec:no "; #endif +#if defined(TARGET_ANDROID) + hwSupport += "MediaCodec:yes "; +#else + hwSupport += "MediaCodec:no "; +#endif #if defined(HAVE_LIBOPENMAX) && defined(TARGET_POSIX) hwSupport += "OpenMax:yes "; #elif defined(TARGET_POSIX) @@ -259,6 +267,14 @@ CDVDVideoCodec* CDVDFactoryCodec::CreateVideoCodec(CDVDStreamInfo &hint, unsigne } #endif +#if defined(TARGET_ANDROID) + if (!hint.software && CSettings::Get().GetBool("videoplayer.usemediacodec")) + { + CLog::Log(LOGINFO, "MediaCodec Video Decoder..."); + if ( (pCodec = OpenCodec(new CDVDVideoCodecAndroidMediaCodec(), hint, options)) ) return pCodec; + } +#endif + #if defined(HAVE_LIBOPENMAX) if (CSettings::Get().GetBool("videoplayer.useomx") && !hint.software ) { diff --git a/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodec.h b/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodec.h index 87edaa5353..baee6e0dad 100644 --- a/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodec.h +++ b/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodec.h @@ -39,8 +39,10 @@ class COpenMax; class COpenMaxVideo; struct OpenMaxVideoBuffer; class CStageFrightVideo; +class CDVDMediaCodecInfo; typedef void* EGLImageKHR; + // should be entirely filled by all codecs struct DVDVideoPicture { @@ -76,6 +78,10 @@ struct DVDVideoPicture CStageFrightVideo* stf; EGLImageKHR eglimg; }; + + struct { + CDVDMediaCodecInfo *mediacodec; + }; }; unsigned int iFlags; diff --git a/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp b/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp new file mode 100644 index 0000000000..2447bbbaf4 --- /dev/null +++ b/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp @@ -0,0 +1,1009 @@ +/* + * Copyright (C) 2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +// http://developer.android.com/reference/android/media/MediaCodec.html +// +// Android MediaCodec class can be used to access low-level media codec, +// i.e. encoder/decoder components. (android.media.MediaCodec). Requires +// SDK16+ which is 4.1 Jellybean and above. +// + +#include "DVDVideoCodecAndroidMediaCodec.h" + +#include "Application.h" +#include "ApplicationMessenger.h" +#include "DVDClock.h" +#include "threads/Atomics.h" +#include "utils/BitstreamConverter.h" +#include "utils/CPUInfo.h" +#include "utils/log.h" + +#include "android/jni/ByteBuffer.h" +#include "android/jni/MediaCodec.h" +#include "android/jni/MediaCrypto.h" +#include "android/jni/MediaFormat.h" +#include "android/jni/MediaCodecList.h" +#include "android/jni/MediaCodecInfo.h" +#include "android/jni/Surface.h" +#include "android/jni/SurfaceTexture.h" +#include "android/activity/AndroidFeatures.h" + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +/*****************************************************************************/ +/*****************************************************************************/ +class CNULL_Listener : public CJNISurfaceTextureOnFrameAvailableListener +{ +public: + CNULL_Listener() : CJNISurfaceTextureOnFrameAvailableListener(jni::jhobject(NULL)) {}; + +protected: + virtual void OnFrameAvailable(CJNISurfaceTexture &surface) {}; +}; + +class CDVDMediaCodecOnFrameAvailable : public CEvent, CJNISurfaceTextureOnFrameAvailableListener +{ +public: + CDVDMediaCodecOnFrameAvailable(boost::shared_ptr<CJNISurfaceTexture> &surfaceTexture) + : m_surfaceTexture(surfaceTexture) + { + m_surfaceTexture->setOnFrameAvailableListener(*this); + } + + virtual ~CDVDMediaCodecOnFrameAvailable() + { + // unhook the callback + CNULL_Listener null_listener; + m_surfaceTexture->setOnFrameAvailableListener(null_listener); + } + +protected: + virtual void OnFrameAvailable(CJNISurfaceTexture &surface) + { + Set(); + } + +private: + boost::shared_ptr<CJNISurfaceTexture> m_surfaceTexture; + +}; + +/*****************************************************************************/ +/*****************************************************************************/ +CDVDMediaCodecInfo::CDVDMediaCodecInfo( + int index + , unsigned int texture + , boost::shared_ptr<CJNIMediaCodec> &codec + , boost::shared_ptr<CJNISurfaceTexture> &surfacetexture + , boost::shared_ptr<CDVDMediaCodecOnFrameAvailable> &frameready +) +: m_refs(1) +, m_valid(true) +, m_index(index) +, m_texture(texture) +, m_timestamp(0) +, m_codec(codec) +, m_surfacetexture(surfacetexture) +, m_frameready(frameready) +{ + // paranoid checks + assert(m_index >= 0); + assert(m_texture > 0); + assert(m_codec != NULL); + assert(m_surfacetexture != NULL); + assert(m_frameready != NULL); +} + +CDVDMediaCodecInfo::~CDVDMediaCodecInfo() +{ + assert(m_refs == 0); +} + +CDVDMediaCodecInfo* CDVDMediaCodecInfo::Retain() +{ + AtomicIncrement(&m_refs); + + return this; +} + +long CDVDMediaCodecInfo::Release() +{ + long count = AtomicDecrement(&m_refs); + if (count == 0) + { + ReleaseOutputBuffer(false); + delete this; + } + + return count; +} + +void CDVDMediaCodecInfo::Validate(bool state) +{ + CSingleLock lock(m_section); + + m_valid = state; +} + +void CDVDMediaCodecInfo::ReleaseOutputBuffer(bool render) +{ + CSingleLock lock(m_section); + + if (!m_valid) + return; + + // release OutputBuffer and render if indicated + // then wait for rendered frame to become avaliable. + + if (render) + m_frameready->Reset(); + + m_codec->releaseOutputBuffer(m_index, render); + if (xbmc_jnienv()->ExceptionOccurred()) + { + CLog::Log(LOGERROR, "CDVDMediaCodecInfo::ReleaseOutputBuffer " + "ExceptionOccurred render(%d)", render); + xbmc_jnienv()->ExceptionDescribe(); + xbmc_jnienv()->ExceptionClear(); + } + + // this is key, after calling releaseOutputBuffer, we must + // wait a little for MediaCodec to render to the surface. + // Then we can updateTexImage without delay. If we do not + // wait, then video playback gets jerky. To optomize this, + // we hook the SurfaceTexture OnFrameAvailable callback + // using CJNISurfaceTextureOnFrameAvailableListener and wait + // on a CEvent to fire. 20ms seems to be a good max fallback. + if (render) + m_frameready->WaitMSec(20); +} + +int CDVDMediaCodecInfo::GetTextureID() const +{ + // since m_texture never changes, + // we do not need a m_section lock here. + return m_texture; +} + +void CDVDMediaCodecInfo::GetTransformMatrix(float *textureMatrix) +{ + CSingleLock lock(m_section); + + if (!m_valid) + return; + + m_surfacetexture->getTransformMatrix(textureMatrix); +} + +void CDVDMediaCodecInfo::UpdateTexImage() +{ + CSingleLock lock(m_section); + + if (!m_valid) + return; + + m_surfacetexture->updateTexImage(); + if (xbmc_jnienv()->ExceptionOccurred()) + { + CLog::Log(LOGERROR, "CDVDMediaCodecInfo::UpdateTexImage updateTexImage:ExceptionOccurred"); + xbmc_jnienv()->ExceptionDescribe(); + xbmc_jnienv()->ExceptionClear(); + } + + m_timestamp = m_surfacetexture->getTimestamp(); + if (xbmc_jnienv()->ExceptionOccurred()) + { + CLog::Log(LOGERROR, "CDVDMediaCodecInfo::UpdateTexImage getTimestamp:ExceptionOccurred"); + xbmc_jnienv()->ExceptionDescribe(); + xbmc_jnienv()->ExceptionClear(); + } +} + +/*****************************************************************************/ +/*****************************************************************************/ +CDVDVideoCodecAndroidMediaCodec::CDVDVideoCodecAndroidMediaCodec() +: m_formatname("mediacodec") +, m_opened(false) +, m_surface(NULL) +, m_textureId(0) +, m_bitstream(NULL) +, m_render_sw(false) +{ + memset(&m_videobuffer, 0x00, sizeof(DVDVideoPicture)); +} + +CDVDVideoCodecAndroidMediaCodec::~CDVDVideoCodecAndroidMediaCodec() +{ + Dispose(); +} + +bool CDVDVideoCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) +{ + // check for 4.1 Jellybean and above. + if (CAndroidFeatures::GetVersion() < 16) + return false; + + m_drop = false; + m_hints = hints; + + switch(m_hints.codec) + { + case AV_CODEC_ID_MPEG2VIDEO: + m_mime = "video/mpeg2"; + m_formatname = "amc-mpeg2"; + break; + case AV_CODEC_ID_MPEG4: + m_mime = "video/mp4v-es"; + m_formatname = "amc-mpeg4"; + break; + case AV_CODEC_ID_H263: + m_mime = "video/3gpp"; + m_formatname = "amc-h263"; + break; + case AV_CODEC_ID_VP3: + case AV_CODEC_ID_VP6: + case AV_CODEC_ID_VP6F: + case AV_CODEC_ID_VP8: + //m_mime = "video/x-vp6"; + //m_mime = "video/x-vp7"; + m_mime = "video/x-vnd.on2.vp8"; + m_formatname = "amc-vpX"; + break; + case AV_CODEC_ID_AVS: + case AV_CODEC_ID_CAVS: + case AV_CODEC_ID_H264: + m_mime = "video/avc"; + m_formatname = "amc-h264"; + m_bitstream = new CBitstreamConverter; + if (!m_bitstream->Open(m_hints.codec, (uint8_t*)m_hints.extradata, m_hints.extrasize, true)) + { + SAFE_DELETE(m_bitstream); + return false; + } + break; + case AV_CODEC_ID_VC1: + case AV_CODEC_ID_WMV3: + m_mime = "video/wvc1"; + //m_mime = "video/wmv9"; + m_formatname = "amc-vc1"; + break; + default: + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: Unknown hints.codec(%d)", hints.codec); + return false; + break; + } + + // odroid platform throws trying to updateTexImage with a 'error creating EGLImage' and + // 'unsupported native buffer format (0x13)', sw render them until we figure out why. + if (!m_render_sw) + m_render_sw = g_cpuInfo.getCPUHardware().find("ODROID") != std::string::npos; + + + // CJNIMediaCodec::createDecoderByXXX doesn't handle errors nicely, + // it crashes if the codec isn't found. This is fixed in latest AOSP, + // but not in current 4.1 devices. So 1st search for a matching codec, then create it. + int num_codecs = CJNIMediaCodecList::getCodecCount(); + for (int i = 0; i < num_codecs; i++) + { + CJNIMediaCodecInfo codec_info = CJNIMediaCodecList::getCodecInfoAt(i); + if (codec_info.isEncoder()) + continue; + + std::vector<std::string> types = codec_info.getSupportedTypes(); + // return the 1st one we find, that one is typically 'the best' + for (size_t j = 0; j < types.size(); ++j) + { + if (types[j] == m_mime) + { + m_codecname = codec_info.getName(); + m_codec = boost::shared_ptr<CJNIMediaCodec>(new CJNIMediaCodec(CJNIMediaCodec::createByCodecName(m_codecname))); + + // clear any jni exceptions, jni gets upset if we do not. + if (xbmc_jnienv()->ExceptionOccurred()) + { + CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open ExceptionOccurred"); + xbmc_jnienv()->ExceptionClear(); + m_codec.reset(); + continue; + } + break; + } + } + if (m_codec) + break; + } + if (!m_codec) + { + CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec:: Failed to create Android MediaCodec"); + SAFE_DELETE(m_bitstream); + return false; + } + + ConfigureMediaCodec(); + + // setup a YUV420P DVDVideoPicture buffer. + // first make sure all properties are reset. + memset(&m_videobuffer, 0x00, sizeof(DVDVideoPicture)); + + m_videobuffer.dts = DVD_NOPTS_VALUE; + m_videobuffer.pts = DVD_NOPTS_VALUE; + m_videobuffer.color_range = 0; + m_videobuffer.color_matrix = 4; + m_videobuffer.iFlags = DVP_FLAG_ALLOCATED; + m_videobuffer.iWidth = m_hints.width; + m_videobuffer.iHeight = m_hints.height; + // these will get reset to crop values later + m_videobuffer.iDisplayWidth = m_hints.width; + m_videobuffer.iDisplayHeight = m_hints.height; + + CLog::Log(LOGINFO, "CDVDVideoCodecAndroidMediaCodec:: " + "Open Android MediaCodec %s", m_codecname.c_str()); + + m_opened = true; + + return m_opened; +} + +void CDVDVideoCodecAndroidMediaCodec::Dispose() +{ + m_opened = false; + + // release any retained demux packets + while (!m_demux.empty()) + { + amc_demux &demux_pkt = m_demux.front(); + free(demux_pkt.pData); + m_demux.pop(); + } + + // invalidate any inflight outputbuffers, make sure + // m_output is empty so we do not create new ones + m_input.clear(); + m_output.clear(); + FlushInternal(); + + // clear m_videobuffer bits + if (m_render_sw) + { + free(m_videobuffer.data[0]), m_videobuffer.data[0] = NULL; + free(m_videobuffer.data[1]), m_videobuffer.data[1] = NULL; + free(m_videobuffer.data[2]), m_videobuffer.data[2] = NULL; + } + m_videobuffer.iFlags = 0; + // m_videobuffer.mediacodec is unioned with m_videobuffer.data[0] + // so be very careful when and how you touch it. + m_videobuffer.mediacodec = NULL; + + if (m_codec) + { + m_codec->stop(); + m_codec->release(); + m_codec.reset(); + } + ReleaseSurfaceTexture(); + + SAFE_DELETE(m_bitstream); +} + +int CDVDVideoCodecAndroidMediaCodec::Decode(uint8_t *pData, int iSize, double dts, double pts) +{ + // Handle input, add demuxer packet to input queue, we must accept it or + // it will be discarded as DVDPlayerVideo has no concept of "try again". + // we must return VC_BUFFER or VC_PICTURE, default to VC_BUFFER. + int rtn = VC_BUFFER; + + if (!m_opened) + return rtn; + + if (m_hints.ptsinvalid) + pts = DVD_NOPTS_VALUE; + + // must check for an output picture 1st, + // otherwise, mediacodec can stall on some devices. + if (GetOutputPicture() > 0) + rtn |= VC_PICTURE; + + if (pData) + { + if (m_bitstream) + { + m_bitstream->Convert(pData, iSize); + iSize = m_bitstream->GetConvertSize(); + pData = m_bitstream->GetConvertBuffer(); + } + + // queue demux pkt in case we cannot get an input buffer + amc_demux demux_pkt; + demux_pkt.dts = dts; + demux_pkt.pts = pts; + demux_pkt.iSize = iSize; + demux_pkt.pData = (uint8_t*)malloc(iSize); + memcpy(demux_pkt.pData, pData, iSize); + m_demux.push(demux_pkt); + + // try to fetch an input buffer + int64_t timeout_us = 5000; + int index = m_codec->dequeueInputBuffer(timeout_us); + if (index >= 0) + { + // docs lie, getInputBuffers should be good after + // m_codec->start() but the internal refs are not + // setup until much later on some devices. + if (m_input.empty()) + m_input = m_codec->getInputBuffers(); + + // we have an input buffer, fill it. + int size = m_input[index].capacity(); + // fetch the front demux packet + amc_demux &demux_pkt = m_demux.front(); + if (demux_pkt.iSize > size) + { + CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Decode, iSize(%d) > size(%d)", iSize, size); + demux_pkt.iSize = size; + } + // fetch a pointer to the ByteBuffer backing store + void *dst_ptr = xbmc_jnienv()->GetDirectBufferAddress(m_input[index].get_raw()); + if (dst_ptr) + memcpy(dst_ptr, demux_pkt.pData, demux_pkt.iSize); + + free(demux_pkt.pData); + m_demux.pop(); + + // Translate from dvdplayer dts/pts to MediaCodec pts, + // pts WILL get re-ordered by MediaCodec if needed. + // Do not try to pass pts as a unioned double/int64_t, + // some android devices will diddle with presentationTimeUs + // and you will get NaN back and DVDPlayerVideo will barf. + int64_t presentationTimeUs = AV_NOPTS_VALUE; + if (demux_pkt.pts != DVD_NOPTS_VALUE) + presentationTimeUs = demux_pkt.pts; + else if (demux_pkt.dts != DVD_NOPTS_VALUE) + presentationTimeUs = demux_pkt.dts; +/* + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: " + "pts(%f), ipts(%lld), iSize(%d), GetDataSize(%d), loop_cnt(%d)", + presentationTimeUs, pts_dtoi(presentationTimeUs), iSize, GetDataSize(), loop_cnt); +*/ + int flags = 0; + int offset = 0; + m_codec->queueInputBuffer(index, offset, demux_pkt.iSize, presentationTimeUs, flags); + // clear any jni exceptions, jni gets upset if we do not. + if (xbmc_jnienv()->ExceptionOccurred()) + { + CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Decode ExceptionOccurred"); + xbmc_jnienv()->ExceptionClear(); + } + } + } + + return rtn; +} + +void CDVDVideoCodecAndroidMediaCodec::Reset() +{ + if (!m_opened) + return; + + // dump any pending demux packets + while (!m_demux.empty()) + { + amc_demux &demux_pkt = m_demux.front(); + free(demux_pkt.pData); + m_demux.pop(); + } + + if (m_codec) + { + // flush all outputbuffers inflight, they will + // become invalid on m_codec->flush and generate + // a spew of java exceptions if used + FlushInternal(); + + // now we can flush the actual MediaCodec object + m_codec->flush(); + if (xbmc_jnienv()->ExceptionOccurred()) + { + CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Reset ExceptionOccurred"); + xbmc_jnienv()->ExceptionClear(); + } + + // Invalidate our local DVDVideoPicture bits + m_videobuffer.pts = DVD_NOPTS_VALUE; + if (!m_render_sw) + m_videobuffer.mediacodec = NULL; + } +} + +bool CDVDVideoCodecAndroidMediaCodec::GetPicture(DVDVideoPicture* pDvdVideoPicture) +{ + if (!m_opened) + return false; + + *pDvdVideoPicture = m_videobuffer; + + // Invalidate our local DVDVideoPicture bits + m_videobuffer.pts = DVD_NOPTS_VALUE; + if (!m_render_sw) + m_videobuffer.mediacodec = NULL; + + return true; +} + +bool CDVDVideoCodecAndroidMediaCodec::ClearPicture(DVDVideoPicture* pDvdVideoPicture) +{ + if (pDvdVideoPicture->format == RENDER_FMT_MEDIACODEC) + SAFE_RELEASE(pDvdVideoPicture->mediacodec); + memset(pDvdVideoPicture, 0x00, sizeof(DVDVideoPicture)); + + return true; +} + +void CDVDVideoCodecAndroidMediaCodec::SetDropState(bool bDrop) +{ + m_drop = bDrop; + if (m_drop) + m_videobuffer.iFlags |= DVP_FLAG_DROPPED; + else + m_videobuffer.iFlags &= ~DVP_FLAG_DROPPED; +} + +int CDVDVideoCodecAndroidMediaCodec::GetDataSize(void) +{ + // just ignore internal buffering contribution. + return 0; +} + +double CDVDVideoCodecAndroidMediaCodec::GetTimeSize(void) +{ + // just ignore internal buffering contribution. + return 0.0; +} + +unsigned CDVDVideoCodecAndroidMediaCodec::GetAllowedReferences() +{ + return 3; +} + +void CDVDVideoCodecAndroidMediaCodec::FlushInternal() +{ + // invalidate any existing inflight buffers and create + // new ones to match the number of output buffers + + if (m_render_sw) + return; + + for (size_t i = 0; i < m_inflight.size(); i++) + m_inflight[i]->Validate(false); + m_inflight.clear(); + + for (size_t i = 0; i < m_output.size(); i++) + { + m_inflight.push_back( + new CDVDMediaCodecInfo(i, m_textureId, m_codec, m_surfaceTexture, m_frameAvailable) + ); + } +} + +void CDVDVideoCodecAndroidMediaCodec::ConfigureMediaCodec(void) +{ + // setup a MediaFormat to match the video content, + // used by codec during configure + CJNIMediaFormat mediaformat = CJNIMediaFormat::createVideoFormat( + m_mime.c_str(), m_hints.width, m_hints.height); + // some android devices forget to default the demux input max size + mediaformat.setInteger(CJNIMediaFormat::KEY_MAX_INPUT_SIZE, 0); + + // handle codec extradata + if (m_hints.extrasize) + { + size_t size = m_hints.extrasize; + void *src_ptr = m_hints.extradata; + if (m_bitstream) + { + size = m_bitstream->GetExtraSize(); + src_ptr = m_bitstream->GetExtraData(); + } + // Allocate a byte buffer via allocateDirect in java instead of NewDirectByteBuffer, + // since the latter doesn't allocate storage of its own, and we don't know how long + // the codec uses the buffer. + CJNIByteBuffer bytebuffer = CJNIByteBuffer::allocateDirect(size); + void *dts_ptr = xbmc_jnienv()->GetDirectBufferAddress(bytebuffer.get_raw()); + memcpy(dts_ptr, src_ptr, size); + // codec will automatically handle buffers as extradata + // using entries with keys "csd-0", "csd-1", etc. + mediaformat.setByteBuffer("csd-0", bytebuffer); + } + + InitSurfaceTexture(); + + // configure and start the codec. + // use the MediaFormat that we have setup. + // use a null MediaCrypto, our content is not encrypted. + // use a null Surface, we will extract the video picture data manually. + int flags = 0; + CJNIMediaCrypto crypto(jni::jhobject(NULL)); + // our jni gets upset if we do this a different + // way, do not mess with it. + if (m_render_sw) + { + CJNISurface surface(jni::jhobject(NULL)); + m_codec->configure(mediaformat, surface, crypto, flags); + } + else + { + m_codec->configure(mediaformat, *m_surface, crypto, flags); + } + + m_codec->start(); + + // always, check/clear jni exceptions. + if (xbmc_jnienv()->ExceptionOccurred()) + xbmc_jnienv()->ExceptionClear(); +} + +int CDVDVideoCodecAndroidMediaCodec::GetOutputPicture(void) +{ + int rtn = 0; + + int64_t timeout_us = 5000; + CJNIMediaCodecBufferInfo bufferInfo; + int index = m_codec->dequeueOutputBuffer(bufferInfo, timeout_us); + if (index >= 0) + { + if (m_drop) + { + m_codec->releaseOutputBuffer(index, false); + if (xbmc_jnienv()->ExceptionOccurred()) + xbmc_jnienv()->ExceptionClear(); + return 0; + } + + // some devices will return a valid index + // before signaling INFO_OUTPUT_BUFFERS_CHANGED which + // is used to setup m_output, D'uh. setup m_output here. + if (m_output.empty()) + { + m_output = m_codec->getOutputBuffers(); + FlushInternal(); + } + + int flags = bufferInfo.flags(); + if (flags & CJNIMediaCodec::BUFFER_FLAG_SYNC_FRAME) + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: BUFFER_FLAG_SYNC_FRAME"); + + if (flags & CJNIMediaCodec::BUFFER_FLAG_CODEC_CONFIG) + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: BUFFER_FLAG_CODEC_CONFIG"); + + if (flags & CJNIMediaCodec::BUFFER_FLAG_END_OF_STREAM) + { + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: BUFFER_FLAG_END_OF_STREAM"); + m_codec->releaseOutputBuffer(index, false); + if (xbmc_jnienv()->ExceptionOccurred()) + xbmc_jnienv()->ExceptionClear(); + return 0; + } + + if (!m_render_sw) + { + m_videobuffer.mediacodec = m_inflight[index]->Retain(); + m_videobuffer.mediacodec->Validate(true); + } + else + { + int size = bufferInfo.size(); + int offset = bufferInfo.offset(); + + if (!m_output[index].isDirect()) + CLog::Log(LOGWARNING, "CDVDVideoCodecAndroidMediaCodec:: m_output[index].isDirect == false"); + + if (size && m_output[index].capacity()) + { + uint8_t *src_ptr = (uint8_t*)xbmc_jnienv()->GetDirectBufferAddress(m_output[index].get_raw()); + src_ptr += offset; + + int loop_end; + if (m_videobuffer.format == RENDER_FMT_YUV420P) + loop_end = 3; + else if (m_videobuffer.format == RENDER_FMT_NV12) + loop_end = 2; + + for (int i = 0; i < loop_end; i++) + { + uint8_t *src = src_ptr + m_src_offset[i]; + int src_stride = m_src_stride[i]; + uint8_t *dst = m_videobuffer.data[i]; + int dst_stride = m_videobuffer.iLineSize[i]; + + int height = m_videobuffer.iHeight; + if (i > 0) + height = (m_videobuffer.iHeight + 1) / 2; + + for (int j = 0; j < height; j++, src += src_stride, dst += dst_stride) + memcpy(dst, src, dst_stride); + } + } + m_codec->releaseOutputBuffer(index, false); + } + + int64_t pts= bufferInfo.presentationTimeUs(); + m_videobuffer.dts = DVD_NOPTS_VALUE; + m_videobuffer.pts = DVD_NOPTS_VALUE; + if (pts != AV_NOPTS_VALUE) + m_videobuffer.pts = pts; + +/* + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture " + "index(%d), pts(%f)", index, m_videobuffer.pts); +*/ + // always, check/clear jni exceptions. + if (xbmc_jnienv()->ExceptionOccurred()) + xbmc_jnienv()->ExceptionClear(); + + rtn = 1; + } + else if (index == CJNIMediaCodec::INFO_OUTPUT_BUFFERS_CHANGED) + { + m_output = m_codec->getOutputBuffers(); + FlushInternal(); + } + else if (index == CJNIMediaCodec::INFO_OUTPUT_FORMAT_CHANGED) + { + OutputFormatChanged(); + } + else if (index == CJNIMediaCodec::INFO_TRY_AGAIN_LATER) + { + // normal dequeueOutputBuffer timeout, ignore it. + rtn = -1; + } + else + { + // we should never get here + CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture unknown index(%d)", index); + } + + return rtn; +} + +void CDVDVideoCodecAndroidMediaCodec::OutputFormatChanged(void) +{ + CJNIMediaFormat mediaformat = m_codec->getOutputFormat(); + + int width = mediaformat.getInteger("width"); + int height = mediaformat.getInteger("height"); + int stride = mediaformat.getInteger("stride"); + int slice_height= mediaformat.getInteger("slice-height"); + int color_format= mediaformat.getInteger("color-format"); + int crop_left = mediaformat.getInteger("crop-left"); + int crop_top = mediaformat.getInteger("crop-top"); + int crop_right = mediaformat.getInteger("crop-right"); + int crop_bottom = mediaformat.getInteger("crop-bottom"); + + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: " + "width(%d), height(%d), stride(%d), slice-height(%d), color-format(%d)", + width, height, stride, slice_height, color_format); + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: " + "crop-left(%d), crop-top(%d), crop-right(%d), crop-bottom(%d)", + crop_left, crop_top, crop_right, crop_bottom); + + if (!m_render_sw) + { + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: Direct Surface Rendering"); + m_videobuffer.format = RENDER_FMT_MEDIACODEC; + } + else + { + // Android device quirks and fixes + if (stride <= 0) + stride = width; + if (slice_height <= 0) + { + slice_height = height; + if (color_format == CJNIMediaCodecInfoCodecCapabilities::COLOR_FormatYUV420Planar) + { + // NVidia Tegra 3 on Nexus 7 does not set slice_heights + if (strstr(m_codecname.c_str(), "OMX.Nvidia.") != NULL) + { + slice_height = (((height) + 31) & ~31); + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: NVidia Tegra 3 quirk, slice_height(%d)", slice_height); + } + } + } + if (color_format == CJNIMediaCodecInfoCodecCapabilities::COLOR_TI_FormatYUV420PackedSemiPlanar) + { + slice_height -= crop_top / 2; + // set crop top/left here, since the offset parameter already includes this. + // if we would ignore the offset parameter in the BufferInfo, we could just keep + // the original slice height and apply the top/left cropping instead. + crop_top = 0; + crop_left = 0; + } + + // default picture format to none + for (int i = 0; i < 4; i++) + m_src_offset[i] = m_src_stride[i] = 0; + // delete any existing buffers + for (int i = 0; i < 4; i++) + free(m_videobuffer.data[i]); + + // setup picture format and data offset vectors + if (color_format == CJNIMediaCodecInfoCodecCapabilities::COLOR_FormatYUV420Planar) + { + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: COLOR_FormatYUV420Planar"); + + // Y plane + m_src_stride[0] = stride; + m_src_offset[0] = crop_top * stride; + m_src_offset[0]+= crop_left; + + // U plane + m_src_stride[1] = (stride + 1) / 2; + // skip over the Y plane + m_src_offset[1] = slice_height * stride; + // crop_top/crop_left divided by two + // because one byte of the U/V planes + // corresponds to two pixels horizontally/vertically + m_src_offset[1]+= crop_top / 2 * m_src_stride[1]; + m_src_offset[1]+= crop_left / 2; + + // V plane + m_src_stride[2] = (stride + 1) / 2; + // skip over the Y plane + m_src_offset[2] = slice_height * stride; + // skip over the U plane + m_src_offset[2]+= ((slice_height + 1) / 2) * ((stride + 1) / 2); + // crop_top/crop_left divided by two + // because one byte of the U/V planes + // corresponds to two pixels horizontally/vertically + m_src_offset[2]+= crop_top / 2 * m_src_stride[2]; + m_src_offset[2]+= crop_left / 2; + + m_videobuffer.iLineSize[0] = width; // Y + m_videobuffer.iLineSize[1] = (width + 1) /2; // U + m_videobuffer.iLineSize[2] = (width + 1) /2; // V + m_videobuffer.iLineSize[3] = 0; + + unsigned int iPixels = width * height; + unsigned int iChromaPixels = iPixels/4; + m_videobuffer.data[0] = (uint8_t*)malloc(16 + iPixels); + m_videobuffer.data[1] = (uint8_t*)malloc(16 + iChromaPixels); + m_videobuffer.data[2] = (uint8_t*)malloc(16 + iChromaPixels); + m_videobuffer.data[3] = NULL; + m_videobuffer.format = RENDER_FMT_YUV420P; + } + else if (color_format == CJNIMediaCodecInfoCodecCapabilities::COLOR_FormatYUV420SemiPlanar + || color_format == CJNIMediaCodecInfoCodecCapabilities::COLOR_QCOM_FormatYUV420SemiPlanar + || color_format == CJNIMediaCodecInfoCodecCapabilities::COLOR_TI_FormatYUV420PackedSemiPlanar) + { + CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: COLOR_FormatYUV420SemiPlanar"); + + // Y plane + m_src_stride[0] = stride; + m_src_offset[0] = crop_top * stride; + m_src_offset[0]+= crop_left; + + // UV plane + m_src_stride[1] = stride; + // skip over the Y plane + m_src_offset[1] = slice_height * stride; + m_src_offset[1]+= crop_top * stride; + m_src_offset[1]+= crop_left; + + m_videobuffer.iLineSize[0] = width; // Y + m_videobuffer.iLineSize[1] = width; // UV + m_videobuffer.iLineSize[2] = 0; + m_videobuffer.iLineSize[3] = 0; + + unsigned int iPixels = width * height; + unsigned int iChromaPixels = iPixels; + m_videobuffer.data[0] = (uint8_t*)malloc(16 + iPixels); + m_videobuffer.data[1] = (uint8_t*)malloc(16 + iChromaPixels); + m_videobuffer.data[2] = NULL; + m_videobuffer.data[3] = NULL; + m_videobuffer.format = RENDER_FMT_NV12; + } + else + { + CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec:: Fixme unknown color_format(%d)", color_format); + return; + } + } + + // picture display width/height include the cropping. + m_videobuffer.iDisplayWidth = crop_right + 1 - crop_left; + m_videobuffer.iDisplayHeight = crop_bottom + 1 - crop_top; + + // clear any jni exceptions + if (xbmc_jnienv()->ExceptionOccurred()) + xbmc_jnienv()->ExceptionClear(); +} + +void CDVDVideoCodecAndroidMediaCodec::CallbackInitSurfaceTexture(void *userdata) +{ + CDVDVideoCodecAndroidMediaCodec *ctx = static_cast<CDVDVideoCodecAndroidMediaCodec*>(userdata); + ctx->InitSurfaceTexture(); +} + +void CDVDVideoCodecAndroidMediaCodec::InitSurfaceTexture(void) +{ + if (m_render_sw) + return; + + // We MUST create the GLES texture on the main thread + // to match where the valid GLES context is located. + // It would be nice to move this out of here, we would need + // to create/fetch/create from g_RenderMananger. But g_RenderMananger + // does not know we are using MediaCodec until Configure and we + // we need m_surfaceTexture valid before then. Chicken, meet Egg. + if (g_application.IsCurrentThread()) + { + // localize GLuint so we do not spew gles includes in our header + GLuint texture_id; + + glGenTextures(1, &texture_id); + glBindTexture( GL_TEXTURE_EXTERNAL_OES, texture_id); + glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture( GL_TEXTURE_EXTERNAL_OES, 0); + m_textureId = texture_id; + + m_surfaceTexture = boost::shared_ptr<CJNISurfaceTexture>(new CJNISurfaceTexture(m_textureId)); + // hook the surfaceTexture OnFrameAvailable callback + m_frameAvailable = boost::shared_ptr<CDVDMediaCodecOnFrameAvailable>(new CDVDMediaCodecOnFrameAvailable(m_surfaceTexture)); + m_surface = new CJNISurface(*m_surfaceTexture); + } + else + { + ThreadMessageCallback callbackData; + callbackData.callback = &CallbackInitSurfaceTexture; + callbackData.userptr = (void*)this; + + ThreadMessage msg; + msg.dwMessage = TMSG_CALLBACK; + msg.lpVoid = (void*)&callbackData; + + // wait for it. + CApplicationMessenger::Get().SendMessage(msg, true); + } + + return; +} + +void CDVDVideoCodecAndroidMediaCodec::ReleaseSurfaceTexture(void) +{ + if (m_render_sw) + return; + + // it is safe to delete here even though these items + // were created in the main thread instance + SAFE_DELETE(m_surface); + m_frameAvailable.reset(); + m_surfaceTexture.reset(); + + if (m_textureId > 0) + { + GLuint texture_id = m_textureId; + glDeleteTextures(1, &texture_id); + m_textureId = 0; + } +} diff --git a/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h b/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h new file mode 100644 index 0000000000..7e2c069a1e --- /dev/null +++ b/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h @@ -0,0 +1,142 @@ +#pragma once +/* + * Copyright (C) 2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include <queue> +#include <vector> +#include <boost/shared_ptr.hpp> + +#include "DVDVideoCodec.h" +#include "DVDStreamInfo.h" +#include "threads/Thread.h" +#include "threads/SingleLock.h" + +class CJNISurface; +class CJNISurfaceTexture; +class CJNIMediaCodec; +class CDVDMediaCodecOnFrameAvailable; +class CJNIByteBuffer; +class CBitstreamConverter; + +typedef struct amc_demux { + uint8_t *pData; + int iSize; + double dts; + double pts; +} amc_demux; + +class CDVDMediaCodecInfo +{ +public: + CDVDMediaCodecInfo( int index, + unsigned int texture, + boost::shared_ptr<CJNIMediaCodec> &codec, + boost::shared_ptr<CJNISurfaceTexture> &surfacetexture, + boost::shared_ptr<CDVDMediaCodecOnFrameAvailable> &frameready); + + // reference counting + CDVDMediaCodecInfo* Retain(); + long Release(); + + // meat and potatos + void Validate(bool state); + // MediaCodec related + void ReleaseOutputBuffer(bool render); + // SurfaceTexture released + int GetTextureID() const; + void GetTransformMatrix(float *textureMatrix); + void UpdateTexImage(); + +private: + // private because we are reference counted + virtual ~CDVDMediaCodecInfo(); + + long m_refs; + bool m_valid; + int m_index; + unsigned int m_texture; + int64_t m_timestamp; + CCriticalSection m_section; + // shared_ptr bits, shared between + // CDVDVideoCodecAndroidMediaCodec and LinuxRenderGLES. + boost::shared_ptr<CJNIMediaCodec> m_codec; + boost::shared_ptr<CJNISurfaceTexture> m_surfacetexture; + boost::shared_ptr<CDVDMediaCodecOnFrameAvailable> m_frameready; +}; + +class CDVDVideoCodecAndroidMediaCodec : public CDVDVideoCodec +{ +public: + CDVDVideoCodecAndroidMediaCodec(); + virtual ~CDVDVideoCodecAndroidMediaCodec(); + + // required overrides + virtual bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options); + virtual void Dispose(); + virtual int Decode(uint8_t *pData, int iSize, double dts, double pts); + virtual void Reset(); + virtual bool GetPicture(DVDVideoPicture *pDvdVideoPicture); + virtual bool ClearPicture(DVDVideoPicture* pDvdVideoPicture); + virtual void SetDropState(bool bDrop); + virtual int GetDataSize(void); + virtual double GetTimeSize(void); + virtual const char* GetName(void) { return m_formatname; } + virtual unsigned GetAllowedReferences(); + +protected: + void FlushInternal(void); + void ConfigureMediaCodec(void); + int GetOutputPicture(void); + void OutputFormatChanged(void); + + // surface handling functions + static void CallbackInitSurfaceTexture(void*); + void InitSurfaceTexture(void); + void ReleaseSurfaceTexture(void); + + CDVDStreamInfo m_hints; + std::string m_mime; + std::string m_codecname; + const char *m_formatname; + bool m_opened; + bool m_drop; + + CJNISurface *m_surface; + unsigned int m_textureId; + // we need these as shared_ptr because CDVDVideoCodecAndroidMediaCodec + // will get deleted before CLinuxRendererGLES is shut down and + // CLinuxRendererGLES refs them via CDVDMediaCodecInfo. + boost::shared_ptr<CJNIMediaCodec> m_codec; + boost::shared_ptr<CJNISurfaceTexture> m_surfaceTexture; + boost::shared_ptr<CDVDMediaCodecOnFrameAvailable> m_frameAvailable; + + std::queue<amc_demux> m_demux; + std::vector<CJNIByteBuffer> m_input; + std::vector<CJNIByteBuffer> m_output; + std::vector<CDVDMediaCodecInfo*> m_inflight; + + CBitstreamConverter *m_bitstream; + DVDVideoPicture m_videobuffer; + + bool m_render_sw; + int m_src_offset[4]; + int m_src_stride[4]; +}; diff --git a/xbmc/cores/dvdplayer/DVDCodecs/Video/Makefile.in b/xbmc/cores/dvdplayer/DVDCodecs/Video/Makefile.in index 043f570700..b0819a8475 100644 --- a/xbmc/cores/dvdplayer/DVDCodecs/Video/Makefile.in +++ b/xbmc/cores/dvdplayer/DVDCodecs/Video/Makefile.in @@ -41,6 +41,10 @@ INCLUDES += -I$(prefix)/include/amcodec INCLUDES += -I$(prefix)/include/amplayer endif +ifeq (@USE_ANDROID@,1) +SRCS += DVDVideoCodecAndroidMediaCodec.cpp +endif + LIB=Video.a include @abs_top_srcdir@/Makefile.include diff --git a/xbmc/cores/dvdplayer/DVDPlayerVideo.cpp b/xbmc/cores/dvdplayer/DVDPlayerVideo.cpp index d35751cfe1..2321d2ca7e 100644 --- a/xbmc/cores/dvdplayer/DVDPlayerVideo.cpp +++ b/xbmc/cores/dvdplayer/DVDPlayerVideo.cpp @@ -999,6 +999,7 @@ static std::string GetRenderFormatName(ERenderFormat format) case RENDER_FMT_CVBREF: return "BGRA"; case RENDER_FMT_EGLIMG: return "EGLIMG"; case RENDER_FMT_BYPASS: return "BYPASS"; + case RENDER_FMT_MEDIACODEC:return "MEDIACODEC"; case RENDER_FMT_NONE: return "NONE"; } return "UNKNOWN"; diff --git a/xbmc/guilib/GUIShader.cpp b/xbmc/guilib/GUIShader.cpp index a3eb94ab81..11089b8bef 100644 --- a/xbmc/guilib/GUIShader.cpp +++ b/xbmc/guilib/GUIShader.cpp @@ -39,6 +39,7 @@ CGUIShader::CGUIShader( const char *shader ) : CGLSLShaderProgram("guishader_ver m_hCord0 = 0; m_hCord1 = 0; m_hUniCol = 0; + m_hCoord0Matrix = 0; m_proj = NULL; m_model = NULL; @@ -54,8 +55,11 @@ void CGUIShader::OnCompiledAndLinked() m_hUniCol = glGetUniformLocation(ProgramHandle(), "m_unicol"); // Variables passed directly to the Vertex shader - m_hProj = glGetUniformLocation(ProgramHandle(), "m_proj"); - m_hModel = glGetUniformLocation(ProgramHandle(), "m_model"); + m_hProj = glGetUniformLocation(ProgramHandle(), "m_proj"); + m_hModel = glGetUniformLocation(ProgramHandle(), "m_model"); + m_hCoord0Matrix = glGetUniformLocation(ProgramHandle(), "m_coord0Matrix"); + + // Vertex attributes m_hPos = glGetAttribLocation(ProgramHandle(), "m_attrpos"); m_hCol = glGetAttribLocation(ProgramHandle(), "m_attrcol"); m_hCord0 = glGetAttribLocation(ProgramHandle(), "m_attrcord0"); @@ -66,6 +70,15 @@ void CGUIShader::OnCompiledAndLinked() glUniform1i(m_hTex0, 0); glUniform1i(m_hTex1, 1); glUniform4f(m_hUniCol, 1.0, 1.0, 1.0, 1.0); + + const float identity[16] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + glUniformMatrix4fv(m_hCoord0Matrix, 1, GL_FALSE, identity); + glUseProgram( 0 ); } diff --git a/xbmc/guilib/GUIShader.h b/xbmc/guilib/GUIShader.h index a55c246327..c7e95aa98a 100644 --- a/xbmc/guilib/GUIShader.h +++ b/xbmc/guilib/GUIShader.h @@ -40,7 +40,8 @@ public: GLint GetCord0Loc() { return m_hCord0; } GLint GetCord1Loc() { return m_hCord1; } GLint GetUniColLoc() { return m_hUniCol; } - + GLint GetCoord0MatrixLoc() { return m_hCoord0Matrix; } + protected: GLint m_hTex0; GLint m_hTex1; @@ -51,6 +52,7 @@ protected: GLint m_hCol; GLint m_hCord0; GLint m_hCord1; + GLint m_hCoord0Matrix; GLfloat *m_proj; GLfloat *m_model; diff --git a/xbmc/rendering/gles/RenderSystemGLES.cpp b/xbmc/rendering/gles/RenderSystemGLES.cpp index f43abf4ba4..46015bdd4e 100644 --- a/xbmc/rendering/gles/RenderSystemGLES.cpp +++ b/xbmc/rendering/gles/RenderSystemGLES.cpp @@ -41,6 +41,7 @@ static const char* ShaderNames[SM_ESHADERCOUNT] = "guishader_frag_texture_noblend.glsl", "guishader_frag_multi_blendcolor.glsl", "guishader_frag_rgba.glsl", + "guishader_frag_rgba_oes.glsl", "guishader_frag_rgba_blendcolor.glsl" }; @@ -640,4 +641,12 @@ GLint CRenderSystemGLES::GUIShaderGetUniCol() return -1; } +GLint CRenderSystemGLES::GUIShaderGetCoord0Matrix() +{ + if (m_pGUIshader[m_method]) + return m_pGUIshader[m_method]->GetCoord0MatrixLoc(); + + return -1; +} + #endif diff --git a/xbmc/rendering/gles/RenderSystemGLES.h b/xbmc/rendering/gles/RenderSystemGLES.h index dd4136632d..b0e4a19e90 100644 --- a/xbmc/rendering/gles/RenderSystemGLES.h +++ b/xbmc/rendering/gles/RenderSystemGLES.h @@ -37,6 +37,7 @@ enum ESHADERMETHOD SM_TEXTURE_NOBLEND, SM_MULTI_BLENDCOLOR, SM_TEXTURE_RGBA, + SM_TEXTURE_RGBA_OES, SM_TEXTURE_RGBA_BLENDCOLOR, SM_ESHADERCOUNT }; @@ -76,7 +77,7 @@ public: virtual bool TestRender(); virtual void Project(float &x, float &y, float &z); - + void InitialiseGUIShader(); void EnableGUIShader(ESHADERMETHOD method); void DisableGUIShader(); @@ -86,12 +87,13 @@ public: GLint GUIShaderGetCoord0(); GLint GUIShaderGetCoord1(); GLint GUIShaderGetUniCol(); + GLint GUIShaderGetCoord0Matrix(); protected: virtual void SetVSyncImpl(bool enable) = 0; virtual bool PresentRenderImpl(const CDirtyRegionList &dirty) = 0; void CalculateMaxTexturesize(); - + int m_iVSyncMode; int m_iVSyncErrors; int64_t m_iSwapStamp; diff --git a/xbmc/settings/Settings.cpp b/xbmc/settings/Settings.cpp index be6990d564..da10a5dd50 100644 --- a/xbmc/settings/Settings.cpp +++ b/xbmc/settings/Settings.cpp @@ -82,6 +82,9 @@ #include "utils/XBMCTinyXML.h" #include "view/ViewStateSettings.h" #include "windowing/WindowingFactory.h" +#if defined(TARGET_ANDROID) +#include "android/activity/AndroidFeatures.h" +#endif #if defined(HAS_LIBAMCODEC) #include "utils/AMLUtils.h" @@ -759,6 +762,10 @@ void CSettings::InitializeConditions() #ifdef HAVE_LIBVDPAU m_settingsManager->AddCondition("have_libvdpau"); #endif +#ifdef TARGET_ANDROID + if (CAndroidFeatures::GetVersion() > 15) + m_settingsManager->AddCondition("has_mediacodec"); +#endif #ifdef HAVE_VIDEOTOOLBOXDECODER m_settingsManager->AddCondition("have_videotoolboxdecoder"); if (g_sysinfo.HasVideoToolBoxDecoder()) |