C lib加上tag區分

This commit is contained in:
Raymond Yang 2023-02-09 16:16:49 +08:00
parent 363bfdbfbd
commit 0dca3ce9e8
6 changed files with 91 additions and 60 deletions

View File

@ -1,5 +1,5 @@
package com.ray650128.gstreamer_demo_app package com.ray650128.gstreamer_demo_app
object Constants { object Constants {
const val CONF_DELAY_BASE_MILLIS = 500L const val CONF_DELAY_BASE_MILLIS = 1000L
} }

View File

@ -76,15 +76,15 @@ class MainActivity : AppCompatActivity() {
if (state == ViewPager2.SCROLL_STATE_IDLE && currentPage == oldPage) { if (state == ViewPager2.SCROLL_STATE_IDLE && currentPage == oldPage) {
splitVideoViewAdapter.play(currentPage) splitVideoViewAdapter.play(currentPage)
} }
Log.d("Split", "onPageScrollStateChanged: ${ /*Log.d("Split", "onPageScrollStateChanged: ${
when(state) { when(state) {
ViewPager2.SCROLL_STATE_IDLE -> "SCROLL_STATE_IDLE" ViewPager2.SCROLL_STATE_IDLE -> "SCROLL_STATE_IDLE"
ViewPager2.SCROLL_STATE_DRAGGING -> "SCROLL_STATE_DRAGGING" ViewPager2.SCROLL_STATE_DRAGGING -> "SCROLL_STATE_DRAGGING"
ViewPager2.SCROLL_STATE_SETTLING -> "SCROLL_STATE_SETTLING" ViewPager2.SCROLL_STATE_SETTLING -> "SCROLL_STATE_SETTLING"
else -> "" else -> ""
} }
}") }")*/
Log.d("Split", "oldPage: $oldPage, currentPage: $currentPage") //Log.d("Split", "oldPage: $oldPage, currentPage: $currentPage")
} }
override fun onPageScrolled( override fun onPageScrolled(
position: Int, position: Int,
@ -92,13 +92,13 @@ class MainActivity : AppCompatActivity() {
positionOffsetPixels: Int positionOffsetPixels: Int
) { ) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels) super.onPageScrolled(position, positionOffset, positionOffsetPixels)
Log.d("Split", "onPageScrolled: $currentPage") //Log.d("Split", "onPageScrolled: $currentPage")
} }
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
super.onPageSelected(position) super.onPageSelected(position)
currentPage = position currentPage = position
splitViewModel.activePage.postValue(position) splitViewModel.activePage.postValue(position)
Log.d("Split", "currentPage: $currentPage") //Log.d("Split", "currentPage: $currentPage")
} }
}) })
} }

View File

@ -10,15 +10,6 @@ class MainViewModel: ViewModel() {
private val uriList: List<Device> by lazy { private val uriList: List<Device> by lazy {
listOf( listOf(
Device(
deviceName = "192.168.0.73",
ip = "192.168.0.73",
rtspPort = "554",
account = "admin",
password = "hs22601576",
stream1 = "/media/video1",
stream2 = "/media/video2",
),
Device( Device(
deviceName = "192.168.0.77", deviceName = "192.168.0.77",
ip = "192.168.0.77", ip = "192.168.0.77",
@ -28,6 +19,15 @@ class MainViewModel: ViewModel() {
stream1 = "/media/video1", stream1 = "/media/video1",
stream2 = "/media/video2", stream2 = "/media/video2",
), ),
Device(
deviceName = "192.168.0.73",
ip = "192.168.0.73",
rtspPort = "554",
account = "admin",
password = "hs22601576",
stream1 = "/media/video1",
stream2 = "/media/video2",
),
Device( Device(
deviceName = "192.168.0.79", deviceName = "192.168.0.79",
ip = "192.168.0.79", ip = "192.168.0.79",

View File

@ -18,7 +18,7 @@ import com.ray650128.gstreamer_demo_app.databinding.ItemVideoViewBinding
import com.ray650128.gstreamer_demo_app.extensions.getStreamPath import com.ray650128.gstreamer_demo_app.extensions.getStreamPath
import com.ray650128.gstreamer_demo_app.model.Device import com.ray650128.gstreamer_demo_app.model.Device
class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback { class VideoView : ConstraintLayout, GstCallback {
constructor(context: Context) : super(context) { constructor(context: Context) : super(context) {
initView(context) initView(context)
@ -69,8 +69,9 @@ class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
view = ItemVideoViewBinding.inflate(layoutInflater, this, true) view = ItemVideoViewBinding.inflate(layoutInflater, this, true)
view.baseView.clipToOutline = true view.baseView.clipToOutline = true
videoView.holder.addCallback(this) //videoView.holder.addCallback(this)
gstLibrary = GstLibrary(context) gstLibrary = GstLibrary(context)
gstLibrary.setSurfaceHolder(videoView.holder)
gstLibrary.setOnStatusChangeListener(this) gstLibrary.setOnStatusChangeListener(this)
// View 預設狀態 // View 預設狀態
@ -91,6 +92,7 @@ class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
view.textDeviceName.text = device.deviceName view.textDeviceName.text = device.deviceName
view.textDeviceName.isVisible = true view.textDeviceName.isVisible = true
val rtspUrl = this.data?.getStreamPath(streamType) ?: return // 如果 null 就不指派給 Gstreamer 了 val rtspUrl = this.data?.getStreamPath(streamType) ?: return // 如果 null 就不指派給 Gstreamer 了
gstLibrary.setTag(this.data!!.deviceName)
gstLibrary.setRtspUrl(rtspUrl) gstLibrary.setRtspUrl(rtspUrl)
Log.d("${TAG}_$tag", "Set device to: $device, rtspUrl = $rtspUrl") Log.d("${TAG}_$tag", "Set device to: $device, rtspUrl = $rtspUrl")
} }
@ -100,11 +102,13 @@ class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
} }
fun play() { fun play() {
if (data == null) return
videoView.postInvalidate() videoView.postInvalidate()
gstLibrary.play() gstLibrary.play()
} }
fun stop() { fun stop() {
if (data == null) return
isPlaying = false isPlaying = false
if (this::gstLibrary.isInitialized) { if (this::gstLibrary.isInitialized) {
gstLibrary.stop() gstLibrary.stop()
@ -126,27 +130,6 @@ class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
mHandler.removeCallbacks(retryRunnable) mHandler.removeCallbacks(retryRunnable)
} }
override fun surfaceCreated(holder: SurfaceHolder) {
Log.d("${TAG}_$tag", "Surface created: " + holder.surface)
if (this::gstLibrary.isInitialized) {
gstLibrary.setSurfaceHolder(holder)
}
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
Log.d("${TAG}_$tag", "Surface changed, format: $format, width: $width, height: $height")
/*if (this::gstLibrary.isInitialized) {
gstLibrary.setSurfaceHolder(holder)
}*/
}
override fun surfaceDestroyed(p0: SurfaceHolder) {
Log.d("${TAG}_$tag", "Surface destroyed")
if (this::gstLibrary.isInitialized) {
gstLibrary.releaseSurface()
}
}
override fun onStatus(gstStatus: GstStatus?) { // onStatus 不是在主執行緒,因此透過 Handler 發訊息到主執行緒去執行 override fun onStatus(gstStatus: GstStatus?) { // onStatus 不是在主執行緒,因此透過 Handler 發訊息到主執行緒去執行
when (gstStatus) { when (gstStatus) {
GstStatus.PLAYING -> mHandler.sendMessage(Message().apply { what = MSG_PLAY }) GstStatus.PLAYING -> mHandler.sendMessage(Message().apply { what = MSG_PLAY })

View File

@ -35,6 +35,7 @@ typedef struct _CustomData {
GstState state; /* Current pipeline state */ GstState state; /* Current pipeline state */
GstState target_state; /* Desired pipeline state, to be set once buffering is complete */ GstState target_state; /* Desired pipeline state, to be set once buffering is complete */
gboolean is_live; /* Live streams do not use buffering */ gboolean is_live; /* Live streams do not use buffering */
gchar * tag;
} CustomData; } CustomData;
/* playbin2 flags */ /* playbin2 flags */
@ -94,7 +95,7 @@ static JNIEnv *get_jni_env(void) {
/* Change the content of the UI's TextView */ /* Change the content of the UI's TextView */
static void set_ui_message(const gchar *message, CustomData *data) { static void set_ui_message(const gchar *message, CustomData *data) {
JNIEnv *env = get_jni_env(); JNIEnv *env = get_jni_env();
GST_DEBUG ("Setting message to: %s", message); //GST_DEBUG ("Setting message to: %s", message);
jstring jmessage = (*env)->NewStringUTF(env, message); jstring jmessage = (*env)->NewStringUTF(env, message);
(*env)->CallVoidMethod(env, data->app, set_message_method_id, jmessage); (*env)->CallVoidMethod(env, data->app, set_message_method_id, jmessage);
if ((*env)->ExceptionCheck(env)) { if ((*env)->ExceptionCheck(env)) {
@ -125,8 +126,9 @@ static void error_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
/* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */ /* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */
static void eos_cb(GstBus *bus, GstMessage *msg, CustomData *data) { static void eos_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
data->target_state = GST_STATE_PAUSED; data->target_state = GST_STATE_PAUSED;
data->is_live |= (gst_element_set_state(data->pipeline, GstStateChangeReturn status = gst_element_set_state(data->pipeline,GST_STATE_PAUSED);
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL); data->is_live |= (status == GST_STATE_CHANGE_NO_PREROLL);
GST_DEBUG ("eos_cb, %s", data->is_live ? "true" : "false");
} }
/* Called when buffering messages are received. We inform the UI about the current buffering level and /* Called when buffering messages are received. We inform the UI about the current buffering level and
@ -321,16 +323,16 @@ static void gst_native_finalize(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) if (!data)
return; return;
GST_DEBUG ("Quitting main loop..."); GST_DEBUG ("[%s]Quitting main loop...", data->tag);
g_main_loop_quit(data->main_loop); g_main_loop_quit(data->main_loop);
GST_DEBUG ("Waiting for thread to finish..."); GST_DEBUG ("[%s]Waiting for thread to finish...", data->tag);
pthread_join(gst_app_thread, NULL); pthread_join(gst_app_thread, NULL);
GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app); GST_DEBUG ("[%s]Deleting GlobalRef for app object at %p", data->tag, data->app);
(*env)->DeleteGlobalRef(env, data->app); (*env)->DeleteGlobalRef(env, data->app);
GST_DEBUG ("Freeing CustomData at %p", data); GST_DEBUG ("[%s]Freeing CustomData at %p", data->tag, data);
g_free(data); g_free(data);
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL); SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
GST_DEBUG ("Done finalizing"); GST_DEBUG ("[%s]Done finalizing", data->tag);
} }
/* Set playbin2's URI */ /* Set playbin2's URI */
@ -345,7 +347,21 @@ void gst_native_set_uri(JNIEnv *env, jobject thiz, jstring uri) {
g_object_set(data->pipeline, "uri", char_uri, NULL); g_object_set(data->pipeline, "uri", char_uri, NULL);
(*env)->ReleaseStringUTFChars(env, uri, char_uri); (*env)->ReleaseStringUTFChars(env, uri, char_uri);
g_object_set(data->pipeline, "latency", 250, NULL); g_object_set(data->pipeline, "latency", 250, NULL);
data->is_live = (gst_element_set_state(data->pipeline, data->target_state) == GST_STATE_CHANGE_NO_PREROLL); GstStateChangeReturn status = gst_element_set_state(data->pipeline, data->target_state);
data->is_live = (status == GST_STATE_CHANGE_NO_PREROLL);
GST_DEBUG ("[%s]gst_native_set_uri, %s", data->tag, data->is_live ? "true" : "false");
}
/* Set name */
void gst_native_set_tag(JNIEnv *env, jobject thiz, jstring tag) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data || !data->pipeline)
return;
const gchar *char_tag = (*env)->GetStringUTFChars(env, tag, NULL);
GST_DEBUG ("Setting TAG to %s", char_tag);
data->tag = g_strdup (char_tag);
(*env)->ReleaseStringUTFChars(env, tag, char_tag);
} }
/* Set pipeline to PLAYING state */ /* Set pipeline to PLAYING state */
@ -353,9 +369,12 @@ static void gst_native_play(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) if (!data)
return; return;
GST_DEBUG ("Setting state to PLAYING"); //GST_DEBUG ("Setting state to PLAYING");
data->is_live = (gst_element_set_state(data->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL);
GstStateChangeReturn status = gst_element_set_state(data->pipeline, GST_STATE_PLAYING);
data->is_live = (status == GST_STATE_CHANGE_ASYNC);
data->target_state = GST_STATE_PLAYING; data->target_state = GST_STATE_PLAYING;
GST_DEBUG ("[%s]Setting state to PLAYING, %s", data->tag, data->is_live ? "true" : "false");
} }
/* Set pipeline to PAUSED state */ /* Set pipeline to PAUSED state */
@ -363,9 +382,11 @@ static void gst_native_pause(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) if (!data)
return; return;
GST_DEBUG ("Setting state to PAUSED"); //GST_DEBUG ("Setting state to PAUSED");
data->is_live = (gst_element_set_state(data->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL); GstStateChangeReturn status = gst_element_set_state(data->pipeline, GST_STATE_PAUSED);
data->is_live = (status == GST_STATE_CHANGE_ASYNC);
data->target_state = GST_STATE_PAUSED; data->target_state = GST_STATE_PAUSED;
GST_DEBUG ("[%s]Setting state to PAUSED, %s", data->tag, data->is_live ? "true" : "false");
} }
/* Static class initializer: retrieve method and field IDs */ /* Static class initializer: retrieve method and field IDs */
@ -392,20 +413,18 @@ static void gst_native_surface_init(JNIEnv *env, jobject thiz, jobject surface)
if (!data) if (!data)
return; return;
ANativeWindow *new_native_window = ANativeWindow_fromSurface(env, surface); ANativeWindow *new_native_window = ANativeWindow_fromSurface(env, surface);
GST_DEBUG ("Received surface %p (native window %p)", surface, GST_DEBUG ("[%s]Received surface %p (native window %p)", data->tag, surface, new_native_window);
new_native_window);
if (data->native_window) { if (data->native_window) {
ANativeWindow_release(data->native_window); ANativeWindow_release(data->native_window);
if (data->native_window == new_native_window) { if (data->native_window == new_native_window) {
GST_DEBUG ("New native window is the same as the previous one %p", GST_DEBUG ("[%s]New native window is the same as the previous one %p", data->tag, data->native_window);
data->native_window);
if (data->pipeline) { if (data->pipeline) {
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline)); gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline));
} }
return; return;
} else { } else {
GST_DEBUG ("Released previous native window %p", data->native_window); GST_DEBUG ("[%s]Released previous native window %p", data->tag, data->native_window);
data->initialized = FALSE; data->initialized = FALSE;
} }
} }
@ -418,7 +437,7 @@ static void gst_native_surface_finalize(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) if (!data)
return; return;
GST_DEBUG ("Releasing Native Window %p", data->native_window); GST_DEBUG ("[%s]Releasing Native Window %p", data->tag, data->native_window);
if (data->pipeline) { if (data->pipeline) {
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY (data->pipeline), gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY (data->pipeline),
@ -436,6 +455,7 @@ static JNINativeMethod native_methods[] = {
{"nativeInit", "()V", (void *) gst_native_init}, {"nativeInit", "()V", (void *) gst_native_init},
{"nativeFinalize", "()V", (void *) gst_native_finalize}, {"nativeFinalize", "()V", (void *) gst_native_finalize},
{"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri}, {"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri},
{"nativeSetTag", "(Ljava/lang/String;)V", (void *) gst_native_set_tag},
{"nativePlay", "()V", (void *) gst_native_play}, {"nativePlay", "()V", (void *) gst_native_play},
{"nativePause", "()V", (void *) gst_native_pause}, {"nativePause", "()V", (void *) gst_native_pause},
{"nativeSurfaceInit", "(Ljava/lang/Object;)V", {"nativeSurfaceInit", "(Ljava/lang/Object;)V",

View File

@ -13,7 +13,9 @@ import org.freedesktop.gstreamer.GStreamer;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
public class GstLibrary implements Closeable { public class GstLibrary implements Closeable, SurfaceHolder.Callback {
private static final String TAG = "GStreamer";//GstLibrary.class.getSimpleName();
final Context mAppContext; final Context mAppContext;
@ -21,6 +23,8 @@ public class GstLibrary implements Closeable {
private String rtspUrl; private String rtspUrl;
private String tag = "";
public GstLibrary(Context context) { public GstLibrary(Context context) {
this.mAppContext = context.getApplicationContext(); this.mAppContext = context.getApplicationContext();
@ -38,6 +42,7 @@ public class GstLibrary implements Closeable {
private native void nativeInit(); // Initialize native code, build pipeline, etc private native void nativeInit(); // Initialize native code, build pipeline, etc
private native void nativeFinalize(); // Destroy pipeline and shutdown native code private native void nativeFinalize(); // Destroy pipeline and shutdown native code
private native void nativeSetUri(String uri); // Set the URI of the media to play private native void nativeSetUri(String uri); // Set the URI of the media to play
private native void nativeSetTag(String tag); // Set the URI of the media to play
private native void nativePlay(); // Set pipeline to PLAYING private native void nativePlay(); // Set pipeline to PLAYING
private native void nativePause(); // Set pipeline to PAUSED private native void nativePause(); // Set pipeline to PAUSED
private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks
@ -59,6 +64,11 @@ public class GstLibrary implements Closeable {
this.rtspUrl = rtspUrl; this.rtspUrl = rtspUrl;
} }
public void setTag(String tag) {
this.tag = tag;
nativeSetTag(tag);
}
public void releaseSurface() { public void releaseSurface() {
nativeSurfaceFinalize(); nativeSurfaceFinalize();
} }
@ -68,7 +78,7 @@ public class GstLibrary implements Closeable {
} }
public void setSurfaceHolder(SurfaceHolder holder) { public void setSurfaceHolder(SurfaceHolder holder) {
nativeSurfaceInit(holder.getSurface()); holder.addCallback(this);
} }
// Called from native code. This sets the content of the TextView from the UI thread. // Called from native code. This sets the content of the TextView from the UI thread.
@ -108,7 +118,7 @@ public class GstLibrary implements Closeable {
// Called from native code when the size of the media changes or is first detected. // Called from native code when the size of the media changes or is first detected.
// Inform the video surface about the new size and recalculate the layout. // Inform the video surface about the new size and recalculate the layout.
private void onMediaSizeChanged (int width, int height) { private void onMediaSizeChanged (int width, int height) {
Log.i ("GStreamer", "Media size changed to " + width + "x" + height); Log.i (TAG + "+" + tag, String.format("Media size changed to %d x %d", width, height));
/*mainHandler.post(() -> { /*mainHandler.post(() -> {
surfaceView.media_width = width; surfaceView.media_width = width;
surfaceView.media_height = height; surfaceView.media_height = height;
@ -130,4 +140,22 @@ public class GstLibrary implements Closeable {
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG + "+" + tag, "Surface created: " + holder.getSurface());
nativeSurfaceInit(holder.getSurface());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG + "+" + tag, String.format("Surface changed, format: %d, width: %d, height: %d", format, width, height));
/*setSurfaceHolder(holder);*/
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG + "+" + tag, "Surface destroyed");
releaseSurface();
}
} }