Compare commits

...

4 Commits

Author SHA1 Message Date
Matthew af57c97961 格式化代码 5 months ago
Matthew 12e8cb37e6 RTMPSuck Initial Commit 5 months ago
Matthew c7f98851ca 修改通知栏图标 5 months ago
Matthew e27435e9a4 修改通知栏图标 5 months ago

@ -99,6 +99,14 @@
android:supportsRtl="true"
android:theme="@style/Theme.MicroPhoto"
tools:targetApi="28">
<service
android:name=".RtmpService"
android:enabled="true"
android:exported="true"
>
</service>
<activity
android:name=".LogActivity"
android:exported="false"

@ -474,3 +474,36 @@ target_link_libraries( # Specifies the target library.
)
# set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS_RELEASE "-strip-all")
####################################################################################
### RtmpSuck
####################################################################################
add_definitions(-DRTMPDUMP_VERSION=\"1.0\")
add_library( # Sets the name of the library.
rtmpdump
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
RtmpService.cpp
rtmp/thread.c
rtmp/rtmpsuck.c
)
target_link_libraries( # Specifies the target library.
rtmpdump
PUBLIC -fopenmp -static-openmp
rtmp
${log-lib}
android z
)

@ -0,0 +1,30 @@
//
// Created by Matthew on 2025/1/27.
//
#include <jni.h>
#include <string>
#include <thread>
#include <stdlib.h>
extern "C" {
#include "rtmp/rtmpsuck.h"
}
extern STREAMING_SERVER *rtmpServer;
extern "C"
JNIEXPORT jlong JNICALL
Java_com_xypower_mpapp_RtmpService_startService(JNIEnv *env, jobject thiz) {
// TODO: implement startService()
RtmpSuckMain(0);
return (jlong)rtmpServer;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_xypower_mpapp_RtmpService_stopService(JNIEnv *env, jobject thiz, jlong native_handle) {
stopStreaming(rtmpServer);
// free(rtmpServer);
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,51 @@
//
// Created by Matthew on 2025/1/27.
//
#ifndef MICROPHOTO_RTMPSUCK_H
#define MICROPHOTO_RTMPSUCK_H
#include <librtmp/rtmp_sys.h>
#include <librtmp/log.h>
enum
{
STREAMING_ACCEPTING,
STREAMING_IN_PROGRESS,
STREAMING_STOPPING,
STREAMING_STOPPED
};
typedef struct Flist
{
struct Flist *f_next;
FILE *f_file;
AVal f_path;
} Flist;
typedef struct Plist
{
struct Plist *p_next;
RTMPPacket p_pkt;
} Plist;
typedef struct
{
int sockfd;
int state;
uint32_t stamp;
RTMP rs;
RTMP rc;
Plist *rs_pkt[2]; /* head, tail */
Plist *rc_pkt[2]; /* head, tail */
Flist *f_head, *f_tail;
Flist *f_cur;
} STREAMING_SERVER;
STREAMING_SERVER *startStreaming(const char *address, int port);
void stopStreaming(STREAMING_SERVER * server);
int RtmpSuckMain(int logAll);
#endif //MICROPHOTO_RTMPSUCK_H

@ -0,0 +1,58 @@
/* Thread compatibility glue
* Copyright (C) 2009 Howard Chu
*
* 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 RTMPDump; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
#include "thread.h"
#include <librtmp/log.h>
#ifdef WIN32
#include <errno.h>
HANDLE
ThreadCreate(thrfunc *routine, void *args)
{
HANDLE thd;
thd = (HANDLE) _beginthread(routine, 0, args);
if (thd == -1L)
RTMP_LogPrintf("%s, _beginthread failed with %d\n", __FUNCTION__, errno);
return thd;
}
#else
pthread_t
ThreadCreate(thrfunc *routine, void *args)
{
pthread_t id = 0;
pthread_attr_t attributes;
int ret;
pthread_attr_init(&attributes);
pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED);
ret =
pthread_create(&id, &attributes, routine, args);
if (ret != 0)
RTMP_LogPrintf("%s, pthread_create failed with %d\n", __FUNCTION__, ret);
return id;
}
#endif

@ -0,0 +1,40 @@
/* Thread compatibility glue
* Copyright (C) 2009 Howard Chu
*
* 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 RTMPDump; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
#ifndef __THREAD_H__
#define __THREAD_H__ 1
#ifdef WIN32
#include <windows.h>
#include <process.h>
#define TFTYPE void
#define TFRET()
#define THANDLE HANDLE
#else
#include <pthread.h>
#define TFTYPE void *
#define TFRET() return 0
#define THANDLE pthread_t
#endif
typedef TFTYPE (thrfunc)(void *arg);
THANDLE ThreadCreate(thrfunc *routine, void *args);
#endif /* __THREAD_H__ */

@ -7,16 +7,11 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.FileObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.StrictMode;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
@ -24,9 +19,7 @@ import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
@ -38,7 +31,6 @@ import com.xypower.common.CameraUtils;
import com.xypower.common.MicroPhotoContext;
import com.xypower.mpapp.databinding.ActivityMainBinding;
import com.xypower.mpapp.utils.LocationUtil;
import com.xypower.mpapp.utils.RandomReader;
import java.io.File;
@ -427,6 +419,28 @@ public class MainActivity extends AppCompatActivity {
}
});
this.binding.btnStartRtmp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Context appContext = getApplicationContext();
startRtmpSuckService(appContext);
binding.btnStartRtmp.setEnabled(false);
binding.btnStopRtmp.setEnabled(true);
}
});
this.binding.btnStopRtmp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
RtmpService.stopRtmpService(getApplicationContext());
binding.btnStartRtmp.setEnabled(true);
binding.btnStopRtmp.setEnabled(false);
}
});
}
public static void startMicroPhotoService(Context context, MicroPhotoContext.AppConfig curAppConfig, Messenger messenger) {
@ -454,6 +468,19 @@ public class MainActivity extends AppCompatActivity {
}
}
public static void startRtmpSuckService(Context context) {
Intent intent = new Intent(context, RtmpService.class);
intent.setAction(RtmpService.ACTION_START);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
private void takePhoto(int channel, int preset, boolean photoOrVideo) {
if (binding.btnStartServ.isEnabled()) {
String appPath = MicroPhotoContext.buildMpAppDir(getApplicationContext());

@ -1181,7 +1181,7 @@ public class MicroPhotoService extends Service {
}
notificationBuilder
.setContent(remoteViews)
.setSmallIcon(R.mipmap.ic_launcher)
.setSmallIcon(R.drawable.ic_notification_mp)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setOnlyAlertOnce(true)
.setOngoing(true)

@ -0,0 +1,230 @@
package com.xypower.mpapp;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import java.util.concurrent.atomic.AtomicInteger;
public class RtmpService extends Service {
public RtmpService() {
}
static {
System.loadLibrary("rtmpdump");
}
public static final String TAG = "RTMP";
public static final String ACTION_START = "com.xypower.mprtmp.ACT_START";
public static final String ACTION_STOP = "com.xypower.mprtmp.ACT_STOP";
public static final String ACTION_MAIN = "com.xypower.mprtmp.ACT_MAIN";
public static final int NOTIFICATION_ID_FOREGROUND_SERVICE = 8466603;
private static final String FOREGROUND_CHANNEL_ID = "fg_rtmp";
public static class STATE_SERVICE {
public static final int CONNECTED = 10;
public static final int NOT_CONNECTED = 0;
}
private static int mStateService = STATE_SERVICE.NOT_CONNECTED;
private NotificationManager mNotificationManager;
private ScreenActionReceiver mScreenaActionReceiver = null;
private Handler mHander = null;
private Thread mServiceThread;
private long mNativeHandle = 0;
static AtomicInteger reqCode = new AtomicInteger(100);
private native long startService();
private native void stopService(long nativeHandle);
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
mHander = new Handler();
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mStateService = STATE_SERVICE.NOT_CONNECTED;
mScreenaActionReceiver = new ScreenActionReceiver();
}
@Override
public void onDestroy() {
mStateService = STATE_SERVICE.NOT_CONNECTED;
unregisterReceiver(mScreenaActionReceiver);
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
stopForeground(true);
stopSelf();
return START_NOT_STICKY;
}
// if user starts the service
switch (intent.getAction()) {
case ACTION_START:
Log.d(TAG, "Received user starts foreground intent");
startForeground(NOTIFICATION_ID_FOREGROUND_SERVICE, prepareNotification());
connect();
registerReceiver(mScreenaActionReceiver, mScreenaActionReceiver.getFilter());
if (mServiceThread == null) {
mServiceThread = new Thread(new Runnable() {
@Override
public void run() {
mNativeHandle = startService();
Log.d(TAG, "RTMP service finishes");
}
});
mServiceThread.start();
}
break;
case ACTION_STOP:
unregisterReceiver(mScreenaActionReceiver);
stopForeground(true);
stopSelf();
break;
default:
stopForeground(true);
stopSelf();
}
return START_NOT_STICKY;
}
private void connect() {
// after 10 seconds its connected
mHander.postDelayed(
new Runnable() {
public void run() {
// Log.d(TAG, "Bluetooth Low Energy device is connected!!");
Toast.makeText(getApplicationContext(), "RTMP Connected!", Toast.LENGTH_SHORT).show();
mStateService = STATE_SERVICE.CONNECTED;
startForeground(NOTIFICATION_ID_FOREGROUND_SERVICE, prepareNotification());
}
}, 10000);
}
private Notification prepareNotification() {
// handle build version above android oreo
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O &&
mNotificationManager.getNotificationChannel(FOREGROUND_CHANNEL_ID) == null) {
CharSequence name = getString(R.string.text_name_notification);
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(FOREGROUND_CHANNEL_ID, name, importance);
channel.enableVibration(false);
mNotificationManager.createNotificationChannel(channel);
}
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setAction(ACTION_MAIN);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
// if min sdk goes below honeycomb
/*if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
} else {
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}*/
int uniqueReqCode = reqCode.getAndIncrement();
PendingIntent pendingIntent = PendingIntent.getActivity(this, uniqueReqCode, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// make a stop intent
Intent stopIntent = new Intent(this, RtmpService.class);
stopIntent.setAction(ACTION_STOP);
uniqueReqCode = reqCode.getAndIncrement();
PendingIntent pendingStopIntent = PendingIntent.getService(this, uniqueReqCode, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT);
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification);
remoteViews.setOnClickPendingIntent(R.id.btn_stop, pendingStopIntent);
// if it is connected
switch (mStateService) {
case STATE_SERVICE.NOT_CONNECTED:
remoteViews.setTextViewText(R.id.tv_state, "DISCONNECTED");
break;
case STATE_SERVICE.CONNECTED:
remoteViews.setTextViewText(R.id.tv_state, "CONNECTED");
break;
}
// notification builder
NotificationCompat.Builder notificationBuilder;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
notificationBuilder = new NotificationCompat.Builder(this, FOREGROUND_CHANNEL_ID);
} else {
notificationBuilder = new NotificationCompat.Builder(this);
}
notificationBuilder
.setContent(remoteViews)
.setSmallIcon(R.drawable.ic_rtmpsuck)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
notificationBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
}
return notificationBuilder.build();
}
public static void stopRtmpService(Context context) {
Intent alarmIntent = new Intent();
alarmIntent.setPackage(context.getPackageName());
alarmIntent.setAction(ACTION_STOP);
int uniqueReqCode = reqCode.getAndIncrement();
PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), uniqueReqCode, alarmIntent, 0);
AlarmManager alarmManager = (AlarmManager) context.getApplicationContext().getSystemService(ALARM_SERVICE);
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 100, pendingIntent);
}
}

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#00FF00"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,4h-3.17L15,2L9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,6h4.05l1.83,-2h4.24l1.83,2L20,6v12zM12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zM12,15c-1.65,0 -3,-1.35 -3,-3s1.35,-3 3,-3 3,1.35 3,3 -1.35,3 -3,3z"/>
</vector>

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#07EEB8"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
</vector>

@ -260,6 +260,17 @@
app:layout_constraintStart_toEndOf="@+id/btnTakePhoto3"
app:layout_constraintTop_toTopOf="@+id/btnTakePhoto" />
<Button
android:id="@+id/btnStartRtmp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:minWidth="@dimen/activity_btn_min_width"
android:minHeight="@dimen/activity_btn_min_height"
android:text="RTMP转发"
app:layout_constraintStart_toEndOf="@+id/btnTakePhoto4"
app:layout_constraintTop_toTopOf="@+id/btnTakePhoto4" />
<Button
android:id="@+id/takeVideoBtn"
android:layout_width="wrap_content"
@ -305,6 +316,17 @@
app:layout_constraintStart_toEndOf="@+id/takeVideoBtn3"
app:layout_constraintTop_toTopOf="@+id/takeVideoBtn" />
<Button
android:id="@+id/btnStopRtmp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:minWidth="@dimen/activity_btn_min_width"
android:minHeight="@dimen/activity_btn_min_height"
android:text="停止RTMP"
app:layout_constraintStart_toEndOf="@+id/takeVideoBtn4"
app:layout_constraintTop_toTopOf="@+id/takeVideoBtn4" />
<Button
android:id="@+id/btnSendHb"
android:layout_width="wrap_content"

@ -263,6 +263,17 @@
app:layout_constraintStart_toEndOf="@+id/btnTakePhoto3"
app:layout_constraintTop_toBottomOf="@+id/simchange" />
<Button
android:id="@+id/btnStartRtmp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:minWidth="@dimen/activity_btn_min_width"
android:minHeight="@dimen/activity_btn_min_height"
android:text="RTMP转发"
app:layout_constraintStart_toEndOf="@+id/btnTakePhoto4"
app:layout_constraintTop_toTopOf="@+id/btnTakePhoto4" />
<Button
android:id="@+id/takeVideoBtn"
android:layout_width="wrap_content"
@ -304,6 +315,17 @@
app:layout_constraintStart_toEndOf="@+id/takeVideoBtn3"
app:layout_constraintTop_toTopOf="@+id/takeVideoBtn" />
<Button
android:id="@+id/btnStopRtmp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:minWidth="@dimen/activity_btn_min_width"
android:minHeight="@dimen/activity_btn_min_height"
android:text="停止RTMP"
app:layout_constraintStart_toEndOf="@+id/takeVideoBtn4"
app:layout_constraintTop_toTopOf="@+id/takeVideoBtn4" />
<Button
android:id="@+id/btnSendHb"
android:layout_width="wrap_content"

@ -942,7 +942,7 @@ public class MpMasterService extends Service {
}
notificationBuilder
.setContent(remoteViews)
.setSmallIcon(R.mipmap.ic_launcher)
.setSmallIcon(R.drawable.ic_notification_mst)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setOnlyAlertOnce(true)
.setOngoing(true)

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FF0000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
</vector>
Loading…
Cancel
Save