From 7a99b2b0d2cf8048e1d9dd7fae5ccf984a865b1e Mon Sep 17 00:00:00 2001 From: Peng Li Date: Tue, 22 May 2018 01:49:31 +0800 Subject: [PATCH] Improve Ffmpeg layer --- .../java/ai/suanzi/rtmpclient/FfmpegHelper.java | 34 +++ .../main/java/ai/suanzi/rtmpclient/MyService.java | 8 +- app/src/main/jni/.vim-bookmarks | 3 + app/src/main/jni/Android.mk | 39 ++-- app/src/main/jni/Application.mk | 3 +- app/src/main/jni/FfmpegHelper.cpp | 242 +++++++++++++++++++++ app/src/main/jni/FfmpegHelper.h | 50 +++++ app/src/main/jni/ai_suanzi_rtmpclient_Ffmpeg.cpp | 2 - .../main/jni/ai_suanzi_rtmpclient_FfmpegHelper.cpp | 42 ++++ .../main/jni/ai_suanzi_rtmpclient_FfmpegHelper.h | 37 ++++ 10 files changed, 440 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/ai/suanzi/rtmpclient/FfmpegHelper.java create mode 100644 app/src/main/jni/.vim-bookmarks create mode 100644 app/src/main/jni/FfmpegHelper.cpp create mode 100644 app/src/main/jni/FfmpegHelper.h create mode 100644 app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.cpp create mode 100644 app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.h diff --git a/app/src/main/java/ai/suanzi/rtmpclient/FfmpegHelper.java b/app/src/main/java/ai/suanzi/rtmpclient/FfmpegHelper.java new file mode 100644 index 0000000..5863243 --- /dev/null +++ b/app/src/main/java/ai/suanzi/rtmpclient/FfmpegHelper.java @@ -0,0 +1,34 @@ +package ai.suanzi.rtmpclient; + +import org.apache.log4j.Logger; + +public class FfmpegHelper { + + static { + String arch = System.getProperty("os.arch"); + if (arch.equals("i686")){ + System.loadLibrary("x264"); + } + System.loadLibrary("avutil"); + System.loadLibrary("postproc"); + System.loadLibrary("swresample"); + System.loadLibrary("swscale"); + System.loadLibrary("avcodec"); + System.loadLibrary("avdevice"); + System.loadLibrary("avfilter"); + System.loadLibrary("avformat"); + System.loadLibrary("FfmpegHelperJNI"); + } + + private static Logger gLogger = Logger.getLogger("FfmpegHelper"); + + // callback from native + public static void javaPrint(String string){ + gLogger.error(string); + } + + // native methods + public static native int initialEncoder(int width, int height, String url); + public static native int processFrame(byte[] frame); + public static native int close(); +} diff --git a/app/src/main/java/ai/suanzi/rtmpclient/MyService.java b/app/src/main/java/ai/suanzi/rtmpclient/MyService.java index 0d0136d..a9ef972 100644 --- a/app/src/main/java/ai/suanzi/rtmpclient/MyService.java +++ b/app/src/main/java/ai/suanzi/rtmpclient/MyService.java @@ -32,7 +32,8 @@ public class MyService extends Service implements Camera.PreviewCallback { private static Logger gLogger = Logger.getLogger("MyService"); private static String TAG = "MyService"; - private Ffmpeg ffmpeg = Ffmpeg.getInstance(); + //private Ffmpeg ffmpeg = Ffmpeg.getInstance(); + private FfmpegHelper helper; private Boolean isRunning = false; private Camera mCamera = null; IBinder mBinder = new LocalBinder(); @@ -161,7 +162,7 @@ public class MyService extends Service implements Camera.PreviewCallback { gLogger.error("onPreviewFrame"); } frameCount++; - ffmpeg.process(data); + //ffmpeg.process(data); } public void startPreview (SurfaceHolder holder){ @@ -185,7 +186,8 @@ public class MyService extends Service implements Camera.PreviewCallback { int width = param.getPictureSize().width; int height = param.getPictureSize().height; gLogger.error("setRtmpUrl - size: " + width + "x" + height + ". url: " + url); - int ret = ffmpeg.initnew(width, height, url); + //int ret = ffmpeg.initnew(width, height, url); + int ret = FfmpegHelper.initialEncoder(width, height, url); return ret == 0 ? true : false; } diff --git a/app/src/main/jni/.vim-bookmarks b/app/src/main/jni/.vim-bookmarks new file mode 100644 index 0000000..8643ddf --- /dev/null +++ b/app/src/main/jni/.vim-bookmarks @@ -0,0 +1,3 @@ +let l:bm_file_version = 1 +let l:bm_sessions = {'default': {'/Users/peng/project/yunzhi/RtmpClient/app/src/main/jni/FfmpegHelper.cpp': [{'sign_idx': 9500, 'line_nr': 109, 'content': ' //pCodecCtx->me_range = 16;'},],}} +let l:bm_current_session = 'default' diff --git a/app/src/main/jni/Android.mk b/app/src/main/jni/Android.mk index d6f060b..5a8b542 100644 --- a/app/src/main/jni/Android.mk +++ b/app/src/main/jni/Android.mk @@ -2,8 +2,8 @@ JNI_PATH := $(call my-dir) #include $(JNI_PATH)/libusb-1.0.22/android/jni/Android.mk #include $(JNI_PATH)/libuvc-0.0.6/android/jni/Android.mk -include $(JNI_PATH)/libusb/android/jni/Android.mk -include $(JNI_PATH)/libuvc/android/jni/Android.mk +#include $(JNI_PATH)/libusb/android/jni/Android.mk +#include $(JNI_PATH)/libuvc/android/jni/Android.mk @@ -91,25 +91,36 @@ include $(PREBUILT_SHARED_LIBRARY) # ffmpegjni +#include $(CLEAR_VARS) +#LOCAL_MODULE := ffmpeg-jni +#LOCAL_SRC_FILES := ai_suanzi_rtmpclient_Ffmpeg.cpp +#LOCAL_C_INCLUDES := $(UVC_ROOT)/include +#ifeq ($(TARGET_ARCH),x86) +#LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc x264 +#else +#LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc +#endif +#LOCAL_CFLAGS := -D__ANDROID_API__=21 +#LOCAL_CFLAGS += -I$(LOCAL_PATH)/../ffmpeg-3.0.11/include +#LOCAL_CFLAGS += -I$(FFMPEG_DIR)/include +#LOCAL_CFLAGS += -Ijni/ffmpeg-3.0.11/include +#LOCAL_LDLIBS :=-llog -landroid +#include $(BUILD_SHARED_LIBRARY) + +# FfmpegHelperJNI include $(CLEAR_VARS) -LOCAL_MODULE := ffmpeg-jni -LOCAL_SRC_FILES := ai_suanzi_rtmpclient_Ffmpeg.cpp \ - ai_suanzi_rtmpclient_UVCCamera.cpp \ - UVCCamera.cpp +LOCAL_MODULE := FfmpegHelperJNI +LOCAL_SRC_FILES := ai_suanzi_rtmpclient_FfmpegHelper.cpp \ + FfmpegHelper.cpp LOCAL_C_INCLUDES := $(UVC_ROOT)/include ifeq ($(TARGET_ARCH),x86) -LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc x264 uvc +LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc x264 else -LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc uvc +LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc endif -LOCAL_CFLAGS := -D__ANDROID_API__=21 -#LOCAL_CFLAGS += -I$(LOCAL_PATH)/../ffmpeg-3.0.11/include -#LOCAL_CFLAGS += -I$(FFMPEG_DIR)/include -#LOCAL_CFLAGS += -Ijni/ffmpeg-3.0.11/include - - +#LOCAL_CFLAGS := -D__ANDROID_API__=21 LOCAL_LDLIBS :=-llog -landroid include $(BUILD_SHARED_LIBRARY) diff --git a/app/src/main/jni/Application.mk b/app/src/main/jni/Application.mk index 5965130..f0d9ca8 100644 --- a/app/src/main/jni/Application.mk +++ b/app/src/main/jni/Application.mk @@ -1 +1,2 @@ -APP_ABI := armeabi-v7a x86 \ No newline at end of file +APP_ABI := armeabi-v7a x86 +APP_STL := c++_shared \ No newline at end of file diff --git a/app/src/main/jni/FfmpegHelper.cpp b/app/src/main/jni/FfmpegHelper.cpp new file mode 100644 index 0000000..279341b --- /dev/null +++ b/app/src/main/jni/FfmpegHelper.cpp @@ -0,0 +1,242 @@ +#include "FfmpegHelper.h" +#include "log.h" +#include + +#define FLOGE(...) av_log(NULL, AV_LOG_ERROR, __VA_ARGS__) +#define FLOGD(...) av_log(NULL, AV_LOG_DEBUG, __VA_ARGS__) + +FfmpegHelper* FfmpegHelper::singleton = NULL; +bool FfmpegHelper::isInit = false; + + +FfmpegHelper::FfmpegHelper(JavaVM *vm, jclass cls) +: jvm(vm) +, ai_suanzi_rtmpclient_FfmpegHelper(cls) +{ +} + + +jint FfmpegHelper::nativeOnLoad(JavaVM * vm, void* reserved) +{ + JNIEnv* env; + if(vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK){ + return -1; + } + + jclass local_ref = 0; + if (env) local_ref = env->FindClass ("ai/suanzi/rtmpclient/FfmpegHelper"); + jclass global_ref = reinterpret_cast (env->NewGlobalRef (local_ref)); + singleton = new FfmpegHelper(vm, global_ref); + return JNI_VERSION_1_6; +} + +void FfmpegHelper::av_log_cb (void *ptr, int level, const char* fmt, va_list vl) +{ + static int print_prefix = 1; + char line[1024]; + av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix); + + if (level <= AV_LOG_WARNING){ + if (singleton) singleton->javaPrint(line); + } else { + if (singleton) singleton->javaPrint(line); + //LOGE("%s", line); + } +} + +void FfmpegHelper::javaPrint(const char* str) +{ + JNIEnv* env = 0; + if(this->jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK){ + return; + } + jmethodID mid = env->GetStaticMethodID(ai_suanzi_rtmpclient_FfmpegHelper, "javaPrint", "(Ljava/lang/String;)V"); + jstring jstr = env->NewStringUTF(str); + env->CallStaticVoidMethod(ai_suanzi_rtmpclient_FfmpegHelper, mid, jstr); + env->DeleteLocalRef(jstr); +} + +void FfmpegHelper::init() +{ + av_log_set_callback(av_log_cb); + av_log_set_level(AV_LOG_DEBUG); + FLOGE("########## Ffmpeg Init ##########"); + unsigned int v = avutil_version(); + FLOGE("libavutil - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v)); + v = avcodec_version(); + FLOGE("libavcodec - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v)); + v = avformat_version(); + FLOGE("libavformat - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v)); + v = avdevice_version(); + FLOGE("libavdevice - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v)); + + av_register_all(); + //avdevice_register_all(); + int ret = 0; + if((ret = avformat_network_init()) != 0){ + FLOGE("avformat_network_init, error:%s(%d)", av_err2str(ret), ret); + } + isInit = true; +} + +int FfmpegHelper::initEncoder(int width, int height, const char* outpath) +{ + if(!isInit) init(); + FLOGE("initEncoder - width=%d, height=%d, url=%s", width, height, outpath); + + pWidth = width; + pHeight = height; + int ret = 0; + + avformat_alloc_output_context2(&formatCtx, NULL, "flv", outpath); + + // initial encoder + if((codec = avcodec_find_encoder(AV_CODEC_ID_H264)) == NULL){ + FLOGE("Can not find encoder!\n"); + return -1; + } + codecCtx = avcodec_alloc_context3(codec); + codecCtx->pix_fmt = AV_PIX_FMT_YUV420P; + codecCtx->width = width; + codecCtx->height = height; + codecCtx->time_base.num = 1; + codecCtx->time_base.den = 30; + codecCtx->bit_rate = 800000; + codecCtx->gop_size = 300; + if (formatCtx->oformat->flags & AVFMT_GLOBALHEADER) /* Some formats want stream headers to be separate. */ + codecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER; + //H264 codec param + //pCodecCtx->me_range = 16; + //pCodecCtx->max_qdiff = 4; + //pCodecCtx->qcompress = 0.6; + codecCtx->qmin = 10; + codecCtx->qmax = 51; + //Optional Param + codecCtx->max_b_frames = 3; + // Set H264 preset and tune + AVDictionary *param = 0; + av_dict_set(¶m, "preset", "ultrafast", 0); + av_dict_set(¶m, "tune", "zerolatency", 0); + if ((ret = avcodec_open2(codecCtx, codec, ¶m)) < 0){ + LOGE("Failed to open encoder!, error:%s(%d)\n", av_err2str(ret), ret); + return -1; + } + + //Add a new stream to output,should be called by the user before avformat_write_header() for muxing + AVStream* vStream; + if ((vStream = avformat_new_stream(formatCtx, codec)) == NULL){ + FLOGE("avformat_new_stream - error"); + return -1; + } + vStream->time_base.num = 1; + vStream->time_base.den = 30; + vStream->codec = codecCtx; + + //Open output URL,set before avformat_write_header() for muxing + if (( ret = avio_open(&formatCtx->pb, outpath, AVIO_FLAG_READ_WRITE)) < 0){ + LOGE("Failed to open output file! error :%s(%d)\n", av_err2str(ret), ret); + return -1; + } + + if((ret = avformat_write_header(formatCtx, NULL)) != 0){ //Write File Header + LOGE("avformat_write_header error :%s(%d)\n", av_err2str(ret), ret); + return -1; + } + startTime = av_gettime(); + return 0; +} + +int FfmpegHelper::processFrame(uint8_t* data) +{ + int ret = 0; + AVFrame *pFrame, *pFrameYUV; + pFrameYUV = av_frame_alloc(); + int y_length = pWidth * pHeight; + uint8_t *outBuf = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codecCtx->width, codecCtx->height, 1)); + av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuf, AV_PIX_FMT_YUV420P, codecCtx->width, codecCtx->height, 1); + + memcpy(pFrameYUV->data[0], data, y_length); + for (int i = 0; i < y_length / 4; i++){ + *(pFrameYUV->data[2] + i) = *(data + y_length * 2); + *(pFrameYUV->data[1] + i) = *(data + y_length * 2 + 1); + } + + pFrameYUV->format = AV_PIX_FMT_YUV420P; + pFrameYUV->width = pWidth; + pFrameYUV->height = pHeight; + + + AVPacket encPkt; + encPkt.data = NULL; + encPkt.size = 0; + av_init_packet(&encPkt); + int got_frame = 0; + + avcodec_encode_video2(codecCtx, &encPkt, pFrameYUV, &got_frame); + av_frame_free(&pFrameYUV); + + if(got_frame == 1){ + if (frameCnt % (15 * 60) == 0){ + FLOGE("Succeed to encode frame: %5d\tsize:%5d\n", frameCnt, encPkt.size); + } + frameCnt++; + encPkt.stream_index = vStream->index; + + // PTS + AVRational time_base = formatCtx->streams[0]->time_base;//{ 1, 1000 }; + AVRational r_framerate = {60, 2 };//{ 50, 2 }; + AVRational time_base_q = { 1, AV_TIME_BASE }; + //Duration between 2 frames (us) + int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / av_q2d(r_framerate)); //内部时间戳 + //Parameters + //enc_pkt.pts = (double)(framecnt*calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base)); + encPkt.pts = av_rescale_q(frameCnt*calc_duration, time_base_q, time_base); + encPkt.dts = encPkt.pts; + encPkt.duration = av_rescale_q(calc_duration, time_base_q, time_base); //(double)(calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base)); + encPkt.pos = -1; + + //Delay + int64_t pts_time = av_rescale_q(encPkt.dts, time_base, time_base_q); + int64_t now_time = av_gettime() - startTime; + if (pts_time > now_time) + av_usleep(pts_time - now_time); + + ret = av_interleaved_write_frame(formatCtx, &encPkt); + av_packet_unref(&encPkt); + } + av_free(outBuf); + return 0; +} + +int FfmpegHelper::close() +{ + if(vStream) + avcodec_close(vStream->codec); + avio_close(formatCtx->pb); + avformat_free_context(formatCtx); + return 0; +} + + +jint FfmpegHelper::nativeInitialEncoder(JNIEnv *env, jclass cls, jint width, jint height, jstring url) +{ + const char* output = env->GetStringUTFChars(url, 0); + int ret = 0; + if (singleton) ret = singleton->initEncoder(width, height, output); + env->ReleaseStringUTFChars(url, output); + return ret; +} + +jint FfmpegHelper::nativeProcessFrame(JNIEnv *env, jclass cls, jbyteArray data) +{ + jbyte* buf = (jbyte*)env->GetByteArrayElements(data, 0); + int ret = 0; + if(singleton) ret = singleton->processFrame((uint8_t *)buf); + return ret; +} + +jint FfmpegHelper::nativeClose() +{ + if(singleton) return singleton->close(); + return 0; +} diff --git a/app/src/main/jni/FfmpegHelper.h b/app/src/main/jni/FfmpegHelper.h new file mode 100644 index 0000000..aba871b --- /dev/null +++ b/app/src/main/jni/FfmpegHelper.h @@ -0,0 +1,50 @@ +#ifndef __FFMPEG_HELPER_H__ +#define __FFMPEG_HELPER_H__ + +#include +#include + +extern "C" { + #include "libavformat/avformat.h" + #include "libavcodec/avcodec.h" + #include "libswscale/swscale.h" + #include "libavutil/imgutils.h" + #include "libavutil/time.h" + #include "libavdevice/avdevice.h" +} + +class FfmpegHelper { +public: + static jint nativeOnLoad(JavaVM * jvm, void* reserved); + static jint nativeInitialEncoder(JNIEnv *env, jclass cls, jint width, jint height, jstring url); + static jint nativeProcessFrame(JNIEnv *env, jclass cls, jbyteArray data); + static jint nativeClose(); + +private: + static void av_log_cb (void *ptr, int level, const char* fmt, va_list vl); + void javaPrint(const char* str); + static void init(); + int processFrame(uint8_t *data); + int initEncoder(int width, int height, const char* url); + int close(); + +private: + FfmpegHelper(JavaVM* vm, jclass cls); + +private: + static FfmpegHelper* singleton; + JavaVM *jvm; + jclass ai_suanzi_rtmpclient_FfmpegHelper; + static bool isInit; + + AVFormatContext* formatCtx; + AVCodecContext* codecCtx; + AVCodec* codec; + AVStream* vStream; + int pWidth; + int pHeight; + unsigned int frameCnt; + int64_t startTime; +}; + +#endif /* __FFMPEG_HELPER_H__ */ diff --git a/app/src/main/jni/ai_suanzi_rtmpclient_Ffmpeg.cpp b/app/src/main/jni/ai_suanzi_rtmpclient_Ffmpeg.cpp index 6bfea3a..32f4b67 100644 --- a/app/src/main/jni/ai_suanzi_rtmpclient_Ffmpeg.cpp +++ b/app/src/main/jni/ai_suanzi_rtmpclient_Ffmpeg.cpp @@ -76,7 +76,6 @@ void custom_log(void *ptr, int level, const char* fmt, va_list vl){ if (level <= AV_LOG_WARNING){ LOGE("%s", line); - } else { // LOGE("%s", line); } @@ -142,7 +141,6 @@ JNIEXPORT void JNICALL Java_ai_suanzi_rtmpclient_Ffmpeg_setRtmpUrl (JNIEnv *env, LOGE("set rtmpurl"); //const char* out_path = "rtmp://gpussh.suanzi.ai:1935/myapp/suanzi_ac83f34ead90_cameraid"; //out_path = env->GetStringUTFChars(url, 0); - } diff --git a/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.cpp b/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.cpp new file mode 100644 index 0000000..ee081c2 --- /dev/null +++ b/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.cpp @@ -0,0 +1,42 @@ +// +// Created by Peng Li on 18/5/2018. +// +#include "ai_suanzi_rtmpclient_FfmpegHelper.h" +#include "FfmpegHelper.h" + + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void* reserved) +{ + return FfmpegHelper::nativeOnLoad(vm, reserved); +} + + +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: initialDecoder + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_initialEncoder (JNIEnv *env, jclass cls, jint width, jint height, jstring url) +{ + return FfmpegHelper::nativeInitialEncoder(env, cls, width, height, url); +} + +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: processFrame + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_processFrame(JNIEnv *env, jclass cls, jbyteArray data) +{ + return FfmpegHelper::nativeProcessFrame(env, cls, data); +} + +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: close + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_close (JNIEnv *env, jclass cls) +{ + return FfmpegHelper::nativeClose(); +} diff --git a/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.h b/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.h new file mode 100644 index 0000000..bedc4a8 --- /dev/null +++ b/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.h @@ -0,0 +1,37 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class ai_suanzi_rtmpclient_FfmpegHelper */ + +#ifndef _Included_ai_suanzi_rtmpclient_FfmpegHelper +#define _Included_ai_suanzi_rtmpclient_FfmpegHelper +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: initialEncoder + * Signature: (IILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_initialEncoder + (JNIEnv *, jclass, jint, jint, jstring); + +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: processFrame + * Signature: ([B)I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_processFrame + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: close + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_close + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif -- 2.11.0