diff --git a/Android/app/src/main/cpp/native-lib.cpp b/Android/app/src/main/cpp/native-lib.cpp index 90e29cd4..838d540d 100644 --- a/Android/app/src/main/cpp/native-lib.cpp +++ b/Android/app/src/main/cpp/native-lib.cpp @@ -3,6 +3,8 @@ #include "test_server.cpp" #define JNI_API(retType,funName,...) extern "C" JNIEXPORT retType Java_com_zlmediakit_jni_ZLMediaKit_##funName(JNIEnv* env, jclass cls,##__VA_ARGS__) +#define MediaPlayerCallBackSign "com/zlmediakit/jni/ZLMediaKit$MediaPlayerCallBack" +#define MediaFrameSign "com/zlmediakit/jni/ZLMediaKit$MediaFrame" string stringFromJstring(JNIEnv *env,jstring jstr){ @@ -60,11 +62,69 @@ jbyteArray jbyteArrayFromString(JNIEnv* env, const char* pat,int len = 0){ return jarray; } +jobject makeJavaFrame(JNIEnv* env,const Frame::Ptr &frame){ + static jclass jclass_obj = (jclass)env->NewGlobalRef(env->FindClass(MediaFrameSign)); + static jmethodID jmethodID_init = env->GetMethodID(jclass_obj, "", "()V"); + static jfieldID jfieldID_dts = env->GetFieldID(jclass_obj,"dts","I"); + static jfieldID jfieldID_pts = env->GetFieldID(jclass_obj,"pts","I"); + static jfieldID jfieldID_prefixSize = env->GetFieldID(jclass_obj,"prefixSize","I"); + static jfieldID jfieldID_keyFrame = env->GetFieldID(jclass_obj,"keyFrame","Z"); + static jfieldID jfieldID_data = env->GetFieldID(jclass_obj,"data","[B"); + static jfieldID jfieldID_trackType = env->GetFieldID(jclass_obj,"trackType","I"); + static jfieldID jfieldID_codecId = env->GetFieldID(jclass_obj,"codecId","I"); + + if(!frame){ + return nullptr; + } + jobject ret = env->NewObject(jclass_obj, jmethodID_init); + env->SetIntField(ret,jfieldID_dts,frame->dts()); + env->SetIntField(ret,jfieldID_pts,frame->pts()); + env->SetIntField(ret,jfieldID_prefixSize,frame->prefixSize()); + env->SetBooleanField(ret,jfieldID_keyFrame,frame->keyFrame()); + env->SetObjectField(ret,jfieldID_data,jbyteArrayFromString(env,frame->data(),frame->size())); + env->SetIntField(ret,jfieldID_trackType,frame->getTrackType()); + env->SetIntField(ret,jfieldID_codecId,frame->getCodecId()); + return ret; +} + +static JavaVM *s_jvm = nullptr; + +template +void doInJavaThread(FUN &&fun){ + JNIEnv *env; + int status = s_jvm->GetEnv((void **) &env, JNI_VERSION_1_6); + if (status != JNI_OK) { + if (s_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) { + return; + } + } + fun(env); + if (status != JNI_OK) { + //Detach线程 + s_jvm->DetachCurrentThread(); + } +} + +#define emitEvent(delegate,method,argFmt,...) \ +{ \ + doInJavaThread([&](JNIEnv* env) { \ + static jclass cls = env->GetObjectClass(delegate); \ + static jmethodID jmid = env->GetMethodID(cls, method, argFmt); \ + jobject localRef = env->NewLocalRef(delegate); \ + if(localRef){ \ + env->CallVoidMethod(localRef, jmid, ##__VA_ARGS__); \ + }else{ \ + WarnL << "弱引用已经释放:" << method << " " << argFmt; \ + }\ + }); \ +} + /* * 加载动态库 */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { //设置日志 + s_jvm = vm; Logger::Instance().add(std::make_shared()); Logger::Instance().setWriter(std::make_shared()); InfoL; @@ -100,3 +160,57 @@ JNI_API(jboolean,startDemo,jstring path_to_jni_file){ }); return true; }; + +JNI_API(jlong,createMediaPlayer,jstring url,jobject callback){ + static auto loadFrameClass = makeJavaFrame(env,nullptr); + MediaPlayer::Ptr *ret = new MediaPlayer::Ptr(new MediaPlayer()); + MediaPlayer::Ptr &player = *ret; + + weak_ptr weakPlayer = player; + jobject globalWeakRef = env->NewWeakGlobalRef(callback); + player->setOnPlayResult([weakPlayer,globalWeakRef](const SockException &ex) { + auto strongPlayer = weakPlayer.lock(); + if (!strongPlayer) { + return; + } + emitEvent((jobject)globalWeakRef,"onPlayResult","(ILjava/lang/String;)V",(jint)ex.getErrCode(),env->NewStringUTF(ex.what())); + + if(ex){ + return; + } + + auto viedoTrack = strongPlayer->getTrack(TrackVideo); + if (viedoTrack) { + viedoTrack->addDelegate(std::make_shared([globalWeakRef](const Frame::Ptr &frame) { + emitEvent((jobject)globalWeakRef,"onData","(L" MediaFrameSign ";)V",makeJavaFrame(env,frame)); + })); + } + + auto audioTrack = strongPlayer->getTrack(TrackAudio); + if (audioTrack) { + audioTrack->addDelegate(std::make_shared([globalWeakRef](const Frame::Ptr &frame) { + emitEvent((jobject)globalWeakRef,"onData","(L" MediaFrameSign ";)V",makeJavaFrame(env,frame)); + })); + } + + }); + + player->setOnShutdown([globalWeakRef,weakPlayer](const SockException &ex) { + auto strongPlayer = weakPlayer.lock(); + if (!strongPlayer) { + return; + } + emitEvent((jobject)globalWeakRef,"onShutdown","(ILjava/lang/String;)V",(jint)ex.getErrCode(),env->NewStringUTF(ex.what())); + }); + + player->play(stringFromJstring(env,url)); + + return (jlong)(ret); +} + + + +JNI_API(void,releaseMediaPlayer,jlong ptr){ + MediaPlayer::Ptr *player = (MediaPlayer::Ptr *)ptr; + delete player; +} diff --git a/Android/app/src/main/java/com/zlmediakit/demo/MainActivity.java b/Android/app/src/main/java/com/zlmediakit/demo/MainActivity.java index a0b0bdd7..1c9f2b7a 100644 --- a/Android/app/src/main/java/com/zlmediakit/demo/MainActivity.java +++ b/Android/app/src/main/java/com/zlmediakit/demo/MainActivity.java @@ -6,16 +6,20 @@ import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; +import android.util.Log; import android.widget.Toast; import com.zlmediakit.jni.ZLMediaKit; public class MainActivity extends AppCompatActivity { + public static final String TAG = "ZLMediaKit"; private static String[] PERMISSIONS_STORAGE = { "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.INTERNET"}; + private ZLMediaKit.MediaPlayer _player; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -39,6 +43,31 @@ public class MainActivity extends AppCompatActivity { }else{ Toast.makeText(this,"请给予我权限,否则无法启动测试!" ,Toast.LENGTH_LONG).show(); } + + _player = new ZLMediaKit.MediaPlayer("rtmp://live.hkstv.hk.lxdns.com/live/hks1", new ZLMediaKit.MediaPlayerCallBack() { + @Override + public void onPlayResult(int code, String msg) { + Log.d(TAG,"onPlayResult:" + code + "," + msg); + } + + @Override + public void onShutdown(int code, String msg) { + Log.d(TAG,"onShutdown:" + code + "," + msg); + } + + @Override + public void onData(ZLMediaKit.MediaFrame frame) { + Log.d(TAG,"onData:" + + frame.trackType + "," + + frame.codecId + "," + + frame.dts + "," + + frame.pts + "," + + frame.keyFrame + "," + + frame.prefixSize + "," + + frame.data.length); + } + }); + } } diff --git a/Android/app/src/main/java/com/zlmediakit/jni/ZLMediaKit.java b/Android/app/src/main/java/com/zlmediakit/jni/ZLMediaKit.java index 262dbff3..49b2d55a 100644 --- a/Android/app/src/main/java/com/zlmediakit/jni/ZLMediaKit.java +++ b/Android/app/src/main/java/com/zlmediakit/jni/ZLMediaKit.java @@ -1,8 +1,93 @@ package com.zlmediakit.jni; public class ZLMediaKit { + static public class MediaFrame{ + + /** + * 返回解码时间戳,单位毫秒 + */ + public int dts; + + /** + * 返回显示时间戳,单位毫秒 + */ + public int pts; + + /** + * 前缀长度,譬如264前缀为0x00 00 00 01,那么前缀长度就是4 + * aac前缀则为7个字节 + */ + public int prefixSize; + + /** + * 返回是否为关键帧 + */ + public boolean keyFrame; + + /** + * 音视频数据 + */ + public byte[] data; + + /** + * 是音频还是视频 + * typedef enum { + * TrackInvalid = -1, + * TrackVideo = 0, + * TrackAudio, + * TrackTitle, + * TrackMax = 0x7FFF + * } TrackType; + */ + public int trackType; + + + /** + * 编码类型 + * typedef enum { + * CodecInvalid = -1, + * CodecH264 = 0, + * CodecH265, + * CodecAAC, + * CodecMax = 0x7FFF + * } CodecId; + */ + public int codecId; + } + + static public interface MediaPlayerCallBack{ + void onPlayResult(int code,String msg); + void onShutdown(int code,String msg); + void onData(MediaFrame frame); + }; + + + static public class MediaPlayer{ + private long _ptr; + private MediaPlayerCallBack _callback; + public MediaPlayer(String url,MediaPlayerCallBack callBack){ + _callback = callBack; + _ptr = createMediaPlayer(url,callBack); + } + public void release(){ + if(_ptr != 0){ + releaseMediaPlayer(_ptr); + _ptr = 0; + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + release(); + } + } + + static public native boolean startDemo(String sd_path); + static public native void releaseMediaPlayer(long ptr); + static public native long createMediaPlayer(String url,MediaPlayerCallBack callback); + static { System.loadLibrary("zlmediakit_jni"); } - static public native boolean startDemo(String sd_path); }