diff --git a/jni/nv_omx_dec/build.sh b/jni/nv_omx_dec/build.sh new file mode 100755 index 0000000..7c1c0e5 --- /dev/null +++ b/jni/nv_omx_dec/build.sh @@ -0,0 +1,5 @@ +rm *.o libnv_omx_dec.so +gcc -I /opt/vc/src/hello_pi/libs/ilclient -I /usr/lib/jvm/java-7-openjdk/include -I /usr/lib/jvm/java-7-openjdk/include/linux -I /opt/vc/include -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux -I ./inc -fPIC -L. -c *.c +gcc -shared -Wl,-soname,libnv_omx_dec.so -Wl,--no-undefined -o libnv_omx_dec.so *.o -L. -L /opt/vc/lib/ -lbcm_host -lopenmaxil -lvcos -lvchiq_arm -lpthread -lrt -L /opt/vc/src/hello_pi/libs/ilclient -lilclient +rm *.o + diff --git a/jni/nv_omx_dec/nv_omx_dec.c b/jni/nv_omx_dec/nv_omx_dec.c new file mode 100644 index 0000000..dd67457 --- /dev/null +++ b/jni/nv_omx_dec/nv_omx_dec.c @@ -0,0 +1,253 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Video deocode demo using OpenMAX IL though the ilcient helper library + +#include +#include +#include + +#include "bcm_host.h" +#include "ilclient.h" + +TUNNEL_T tunnel[2]; +COMPONENT_T *list[3]; +ILCLIENT_T *client; + +COMPONENT_T *video_decode = NULL, *video_scheduler = NULL, *video_render = NULL; + +OMX_BUFFERHEADERTYPE *buf; +unsigned char *dest; + +int port_settings_changed; +int first_packet; +int last_packet; + +int nv_omx_init(void) { + bcm_host_init(); + + OMX_VIDEO_PARAM_PORTFORMATTYPE format; + OMX_TIME_CONFIG_CLOCKSTATETYPE cstate; + COMPONENT_T *clock = NULL; + int status = 0; + unsigned int data_len = 0; + int packet_size = 80<<10; + + memset(list, 0, sizeof(list)); + memset(tunnel, 0, sizeof(tunnel)); + + if((client = ilclient_init()) == NULL) + return -3; + + if(OMX_Init() != OMX_ErrorNone) { + ilclient_destroy(client); + return -4; + } + + // create video_decode + if(ilclient_create_component(client, &video_decode, "video_decode", ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS) != 0) + status = -14; + list[0] = video_decode; + + // create video_render + if(status == 0 && ilclient_create_component(client, &video_render, "video_render", ILCLIENT_DISABLE_ALL_PORTS) != 0) + status = -14; + list[1] = video_render; + +/* + // create clock + if(status == 0 && ilclient_create_component(client, &clock, "clock", ILCLIENT_DISABLE_ALL_PORTS) != 0) + status = -14; + list[2] = clock; +*/ + +// memset(&cstate, 0, sizeof(cstate)); +// cstate.nSize = sizeof(cstate); +// cstate.nVersion.nVersion = OMX_VERSION; +// cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; +// cstate.nWaitMask = 1; +// if(clock != NULL && OMX_SetParameter(ILC_GET_HANDLE(clock), OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) +// status = -13; + + // create video_scheduler +// if(status == 0 && ilclient_create_component(client, &video_scheduler, "video_scheduler", ILCLIENT_DISABLE_ALL_PORTS) != 0) +// status = -14; +// list[3] = video_scheduler; + +// set_tunnel(tunnel, video_decode, 131, video_scheduler, 10); +// set_tunnel(tunnel+1, video_scheduler, 11, video_render, 90); +// set_tunnel(tunnel+2, clock, 80, video_scheduler, 12); + set_tunnel(tunnel, video_decode, 131, video_render, 90); + + // setup clock tunnel first +/* + if(status == 0 && ilclient_setup_tunnel(tunnel+2, 0, 0) != 0) + status = -15; + else + ilclient_change_component_state(clock, OMX_StateExecuting); +*/ + + if(status == 0) + ilclient_change_component_state(video_decode, OMX_StateIdle); + + memset(&format, 0, sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE)); + format.nSize = sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE); + format.nVersion.nVersion = OMX_VERSION; + format.nPortIndex = 130; + format.eCompressionFormat = OMX_VIDEO_CodingAVC; + + OMX_PARAM_DATAUNITTYPE unit; + + memset(&unit, 0, sizeof(OMX_PARAM_DATAUNITTYPE)); + unit.nSize = sizeof(OMX_PARAM_DATAUNITTYPE); + unit.nVersion.nVersion = OMX_VERSION; + unit.nPortIndex = 130; + unit.eUnitType = OMX_DataUnitCodedPicture; + unit.eEncapsulationType = OMX_DataEncapsulationElementaryStream; + + if(status == 0 && + OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamVideoPortFormat, &format) == OMX_ErrorNone && + OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamBrcmDataUnit, &unit) == OMX_ErrorNone && +/* OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamBrcmExtraBuffers, 2) == OMX_ErrorNone && */ + ilclient_enable_port_buffers(video_decode, 130, NULL, NULL, NULL) == 0) { + + port_settings_changed = 0; + first_packet = 1; + last_packet = 1; + + ilclient_change_component_state(video_decode, OMX_StateExecuting); + } else + status = -14; + + return status; +} + +int nv_omx_decode(const unsigned char* indata, int data_len, int last) { + if (last_packet) { + last_packet = 0; + + if((buf = ilclient_get_input_buffer(video_decode, 130, 1)) == NULL) + return -22; + + // feed data and wait until we get port settings changed + dest = buf->pBuffer; + + buf->nFilledLen = 0; + + buf->nOffset = 0; + + buf->nFlags = OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS; + + if(first_packet) { + buf->nFlags = OMX_BUFFERFLAG_STARTTIME; + first_packet = 0; + } + } + + memcpy(dest, indata, data_len); + buf->nFilledLen += data_len; + dest += data_len; + + if (last) { + if (buf->nFilledLen == 26) { + buf->nFilledLen += 2; + buf->pBuffer[24] = 0x11; + buf->pBuffer[25] = 0xe3; + buf->pBuffer[26] = 0x06; + buf->pBuffer[27] = 0x50; + } + + if (buf->nFilledLen <=28) { + int i; + for (i=0; inFilledLen; i++) { + printf("%02x ", buf->pBuffer[i]); + } + printf("\n"); + } + + if(port_settings_changed == 0 && + ((data_len > 0 && ilclient_remove_event(video_decode, OMX_EventPortSettingsChanged, 131, 0, 0, 1) == 0) || + (data_len == 0 && ilclient_wait_for_event(video_decode, OMX_EventPortSettingsChanged, 131, 0, 0, 1, + ILCLIENT_EVENT_ERROR | ILCLIENT_PARAMETER_CHANGED, 10000) == 0))) { + port_settings_changed = 1; + + //OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamBrcmDataUnit, OMX_DataUnitCodedPicture); + + if(ilclient_setup_tunnel(tunnel, 0, 0) != 0) + return -7; + + //ilclient_change_component_state(video_scheduler, OMX_StateExecuting); + + // now setup tunnel to video_render + //if(ilclient_setup_tunnel(tunnel+1, 0, 1000) != 0) + // return -12; + + ilclient_change_component_state(video_render, OMX_StateExecuting); + printf("Port changed\n"); + } + + last_packet = last; + + if(OMX_EmptyThisBuffer(ILC_GET_HANDLE(video_decode), buf) != OMX_ErrorNone) + return -6; + } +} + +void nv_omx_stop(void) { + int status = 0; + + buf->nFilledLen = 0; + buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN | OMX_BUFFERFLAG_EOS; + + if(OMX_EmptyThisBuffer(ILC_GET_HANDLE(list[0]), buf) != OMX_ErrorNone) + status = -20; + + // wait for EOS from render +// ilclient_wait_for_event(list[1], OMX_EventBufferFlag, 90, 0, OMX_BUFFERFLAG_EOS, 0, +// ILCLIENT_BUFFER_FLAG_EOS, 10000); + + // need to flush the renderer to allow video_decode to disable its input port + ilclient_flush_tunnels(tunnel, 0); + + ilclient_disable_port_buffers(list[0], 130, NULL, NULL, NULL); +} + +void nv_omx_destroy(void) { + ilclient_disable_tunnel(tunnel); +// ilclient_disable_tunnel(tunnel+1); +// ilclient_disable_tunnel(tunnel+2); + ilclient_teardown_tunnels(tunnel); + + ilclient_state_transition(list, OMX_StateIdle); + ilclient_state_transition(list, OMX_StateLoaded); + + ilclient_cleanup_components(list); + + OMX_Deinit(); + + ilclient_destroy(client); +} diff --git a/jni/nv_omx_dec/nv_omx_dec.h b/jni/nv_omx_dec/nv_omx_dec.h new file mode 100644 index 0000000..a56dfe6 --- /dev/null +++ b/jni/nv_omx_dec/nv_omx_dec.h @@ -0,0 +1,8 @@ +#include + +int nv_omx_init(void); + +int nv_omx_decode(unsigned char* indata, int inlen, int last); + +void nv_omx_stop(void); +void nv_omx_destroy(void); diff --git a/jni/nv_omx_dec/nv_omx_dec_jni.c b/jni/nv_omx_dec/nv_omx_dec_jni.c new file mode 100755 index 0000000..2fdaf58 --- /dev/null +++ b/jni/nv_omx_dec/nv_omx_dec_jni.c @@ -0,0 +1,47 @@ +#include "nv_omx_dec.h" + +#include +#include + +// This function must be called before +// any other decoding functions +JNIEXPORT jint JNICALL +Java_com_limelight_binding_video_OmxDecoder_init(JNIEnv *env, jobject this) +{ + return nv_omx_init(); +} + +// This function must be called after +// decoding is finished +JNIEXPORT void JNICALL +Java_com_limelight_binding_video_OmxDecoder_stop(JNIEnv *env, jobject this) { + nv_omx_stop(); +} + +// This function must be called after +// decoding is finished +JNIEXPORT void JNICALL +Java_com_limelight_binding_video_OmxDecoder_destroy(JNIEnv *env, jobject this) { + nv_omx_destroy(); +} + +// packets must be decoded in order +// the input buffer must have proper padding +// returns 0 on success, < 0 on error +JNIEXPORT jint JNICALL +Java_com_limelight_binding_video_OmxDecoder_decode( + JNIEnv *env, jobject this, // JNI parameters + jbyteArray indata, jint inoff, jint inlen, jboolean last) +{ + jint ret; + jbyte* jni_input_data; + + jni_input_data = (*env)->GetByteArrayElements(env, indata, 0); + + ret = nv_omx_decode(&jni_input_data[inoff], inlen, last); + + // The input data isn't changed so it can be safely aborted + (*env)->ReleaseByteArrayElements(env, indata, jni_input_data, JNI_ABORT); + + return ret; +} diff --git a/src/com/limelight/binding/video/OmxDecoder.java b/src/com/limelight/binding/video/OmxDecoder.java new file mode 100644 index 0000000..51bb90a --- /dev/null +++ b/src/com/limelight/binding/video/OmxDecoder.java @@ -0,0 +1,17 @@ +package com.limelight.binding.video; + +import com.limelight.binding.LibraryHelper; + +public class OmxDecoder { + static { + LibraryHelper.loadNativeLibrary("nv_omx_dec"); + } + + public static native int init(); + + public static native void stop(); + + public static native void destroy(); + + public static native int decode(byte[] indata, int inoff, int inlen, boolean last); +} diff --git a/src/com/limelight/binding/video/OmxDecoderRenderer.java b/src/com/limelight/binding/video/OmxDecoderRenderer.java new file mode 100644 index 0000000..459ffb6 --- /dev/null +++ b/src/com/limelight/binding/video/OmxDecoderRenderer.java @@ -0,0 +1,54 @@ +package com.limelight.binding.video; + +import com.limelight.nvstream.av.ByteBufferDescriptor; +import com.limelight.nvstream.av.DecodeUnit; +import com.limelight.nvstream.av.video.VideoDecoderRenderer; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Implementation of a video decoder and renderer. + * @author Iwan Timmer + */ +public class OmxDecoderRenderer implements VideoDecoderRenderer { + + @Override + public void setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) { + int err = OmxDecoder.init(); + if (err != 0) { + throw new IllegalStateException("AVC decoder initialization failure: "+err); + } + + System.out.println("Using omx decoding"); + } + + @Override + public void start() { + System.out.println("Start omx rendering"); + } + + @Override + public void stop() { + OmxDecoder.stop(); + } + + @Override + public void release() { + OmxDecoder.destroy(); + } + + @Override + public boolean submitDecodeUnit(DecodeUnit decodeUnit) { + boolean ok = true; + List units = decodeUnit.getBufferList(); + for (int i=0;i