1 文件已重命名
15个文件已修改
6个文件已添加
| | |
| | | implementation libs.androidx.ui.tooling.preview |
| | | implementation libs.androidx.material3 |
| | | implementation libs.netty.all |
| | | implementation libs.timber |
| | | testImplementation libs.junit |
| | | androidTestImplementation libs.androidx.junit |
| | | androidTestImplementation libs.androidx.espresso.core |
| | |
| | | <uses-permission android:name="android.permission.INTERNET" /> |
| | | <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
| | | |
| | | <!-- 存储权限(用于日志文件) --> |
| | | <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
| | | <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
| | | |
| | | <!-- USB摄像头权限 --> |
| | | <uses-feature android:name="android.hardware.usb.host" /> |
| | | |
| | | <application |
| | | android:name=".H264Application" |
| | | android:allowBackup="true" |
| | | android:dataExtractionRules="@xml/data_extraction_rules" |
| | | android:fullBackupContent="@xml/backup_rules" |
| | |
| | | * @return 资源列表(根据JT/T 1076-2016表23定义) |
| | | */ |
| | | List<ResourceInfo> getResourceList(String startTime, String endTime); |
| | | |
| | | /** |
| | | * 设置水印信息 |
| | | * @param watermarkInfo 水印信息字符串 |
| | | */ |
| | | void setWatermarkInfo(String watermarkInfo); |
| | | } |
| | | |
| New file |
| | |
| | | package com.anyun.h264; |
| | | |
| | | import android.os.Environment; |
| | | import android.util.Log; |
| | | import timber.log.Timber; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileWriter; |
| | | import java.io.IOException; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | import java.util.Locale; |
| | | |
| | | /** |
| | | * Timber Tree implementation that logs to files. |
| | | * Logs are saved to sdcard/nvlog/h264_yyyyMMdd.log format. |
| | | */ |
| | | public class FileLoggingTree extends Timber.DebugTree { |
| | | private static final String LOG_DIR = "nvlog"; |
| | | private static final String LOG_PREFIX = "h264_"; |
| | | private static final String LOG_SUFFIX = ".log"; |
| | | private static final String DATE_FORMAT = "yyyyMMdd"; |
| | | private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; |
| | | |
| | | private String currentLogFile = null; |
| | | private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.CHINA); |
| | | private SimpleDateFormat timestampFormat = new SimpleDateFormat(TIMESTAMP_FORMAT, Locale.CHINA); |
| | | |
| | | @Override |
| | | protected void log(int priority, String tag, String message, Throwable t) { |
| | | try { |
| | | // Get today's log file path |
| | | String today = dateFormat.format(new Date()); |
| | | String logFileName = LOG_PREFIX + today + LOG_SUFFIX; |
| | | |
| | | // Check if we need to update the log file (new day) |
| | | if (!logFileName.equals(currentLogFile)) { |
| | | currentLogFile = logFileName; |
| | | } |
| | | |
| | | File logFile = getLogFile(logFileName); |
| | | if (logFile == null) { |
| | | return; |
| | | } |
| | | |
| | | // Format log entry |
| | | String logEntry = formatLogEntry(priority, tag, message, t); |
| | | |
| | | // Write to file (append mode) |
| | | synchronized (this) { |
| | | try (FileWriter writer = new FileWriter(logFile, true)) { |
| | | writer.append(logEntry); |
| | | writer.append("\n"); |
| | | writer.flush(); |
| | | } catch (IOException e) { |
| | | // If file writing fails, log to system log as fallback |
| | | Log.e("FileLoggingTree", "Failed to write log to file", e); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | // If anything goes wrong, log to system log |
| | | Log.e("FileLoggingTree", "Error in FileLoggingTree", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get the log file. Creates directory if needed. |
| | | */ |
| | | private File getLogFile(String fileName) { |
| | | try { |
| | | File logDir = new File(Environment.getExternalStorageDirectory(), LOG_DIR); |
| | | if (!logDir.exists()) { |
| | | if (!logDir.mkdirs()) { |
| | | Log.e("FileLoggingTree", "Failed to create log directory: " + logDir.getAbsolutePath()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | File logFile = new File(logDir, fileName); |
| | | if (!logFile.exists()) { |
| | | if (!logFile.createNewFile()) { |
| | | Log.e("FileLoggingTree", "Failed to create log file: " + logFile.getAbsolutePath()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | return logFile; |
| | | } catch (IOException e) { |
| | | Log.e("FileLoggingTree", "Error getting log file", e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Format a log entry with timestamp, priority, tag, and message. |
| | | */ |
| | | private String formatLogEntry(int priority, String tag, String message, Throwable t) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | |
| | | // Timestamp |
| | | sb.append(timestampFormat.format(new Date())); |
| | | sb.append(" "); |
| | | |
| | | // Priority level |
| | | sb.append(getPriorityString(priority)); |
| | | sb.append("/"); |
| | | |
| | | // Tag |
| | | sb.append(tag); |
| | | sb.append(": "); |
| | | |
| | | // Message |
| | | sb.append(message); |
| | | |
| | | // Throwable stack trace |
| | | if (t != null) { |
| | | sb.append("\n"); |
| | | sb.append(Log.getStackTraceString(t)); |
| | | } |
| | | |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * Get priority string representation. |
| | | */ |
| | | private String getPriorityString(int priority) { |
| | | switch (priority) { |
| | | case Log.VERBOSE: |
| | | return "V"; |
| | | case Log.DEBUG: |
| | | return "D"; |
| | | case Log.INFO: |
| | | return "I"; |
| | | case Log.WARN: |
| | | return "W"; |
| | | case Log.ERROR: |
| | | return "E"; |
| | | case Log.ASSERT: |
| | | return "A"; |
| | | default: |
| | | return "?"; |
| | | } |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | package com.anyun.h264; |
| | | |
| | | import android.app.Application; |
| | | import timber.log.Timber; |
| | | |
| | | /** |
| | | * Application class for initializing Timber logging. |
| | | */ |
| | | public class H264Application extends Application { |
| | | @Override |
| | | public void onCreate() { |
| | | super.onCreate(); |
| | | |
| | | // Initialize Timber |
| | | // if (BuildConfig.DEBUG) { |
| | | // In debug mode, use both console and file logging |
| | | Timber.plant(new Timber.DebugTree()); |
| | | Timber.plant(new FileLoggingTree()); |
| | | // } else { |
| | | // // In release mode, only use file logging |
| | | // Timber.plant(new FileLoggingTree()); |
| | | // } |
| | | |
| | | Timber.d("H264Application onCreate - Timber initialized"); |
| | | } |
| | | } |
| | | |
| | |
| | | import android.content.Intent; |
| | | import android.os.IBinder; |
| | | import android.os.RemoteException; |
| | | import android.util.Log; |
| | | import timber.log.Timber; |
| | | |
| | | import com.anyun.h264.model.ResourceInfo; |
| | | import com.anyun.h264.model.WatermarkInfo; |
| | | |
| | | import org.json.JSONException; |
| | | import org.json.JSONObject; |
| | |
| | | private H264Encoder h264Encoder; |
| | | private H264FileTransmitter h264FileTransmitter; // H264文件传输器 |
| | | private String outputFileDirectory; // H264文件输出目录 |
| | | private WatermarkInfo currentWatermarkInfo; // 当前水印信息 |
| | | |
| | | // 默认编码参数 |
| | | private static final int DEFAULT_WIDTH = 640; |
| | |
| | | public List<ResourceInfo> getResourceList(String startTime, String endTime) throws RemoteException { |
| | | return H264EncodeService.this.getResourceList(startTime, endTime); |
| | | } |
| | | |
| | | @Override |
| | | public void setWatermarkInfo(String watermarkInfo) throws RemoteException { |
| | | H264EncodeService.this.setWatermarkInfo(watermarkInfo); |
| | | } |
| | | }; |
| | | |
| | | @Override |
| | | public void onCreate() { |
| | | super.onCreate(); |
| | | Log.d(TAG, "H264EncodeService created"); |
| | | Timber.d("H264EncodeService created"); |
| | | |
| | | // 初始化输出文件目录(使用应用外部存储目录) |
| | | outputFileDirectory = getExternalFilesDir(null).getAbsolutePath(); |
| | | Log.d(TAG, "Output file directory: " + outputFileDirectory); |
| | | Timber.d("Output file directory: %s", outputFileDirectory); |
| | | } |
| | | |
| | | @Override |
| | | public IBinder onBind(Intent intent) { |
| | | Log.d(TAG, "Service bound"); |
| | | Timber.d("Service bound"); |
| | | return binder; |
| | | } |
| | | |
| | | @Override |
| | | public boolean onUnbind(Intent intent) { |
| | | Log.d(TAG, "Service unbound"); |
| | | Timber.d("Service unbound"); |
| | | // 不自动停止编码器,让它在服务中保持运行 |
| | | return super.onUnbind(intent); |
| | | } |
| | |
| | | @Override |
| | | public void onDestroy() { |
| | | super.onDestroy(); |
| | | Log.d(TAG, "Service destroyed"); |
| | | Timber.d("Service destroyed"); |
| | | |
| | | // 停止并释放编码器和文件传输器 |
| | | stopEncoder(); |
| | |
| | | * @return 0-成功,1-失败 |
| | | */ |
| | | private synchronized int controlEncode(int action, String jsonConfig) { |
| | | Log.d(TAG, "controlEncode called with action: " + action + ", jsonConfig: " + jsonConfig); |
| | | Timber.d("controlEncode called with action: %d, jsonConfig: %s", action, jsonConfig); |
| | | |
| | | try { |
| | | switch (action) { |
| | |
| | | EncodeConfig config0 = EncodeConfig.fromJson(jsonConfig); |
| | | return startFileEncode(config0); |
| | | } catch (JSONException e) { |
| | | Log.e(TAG, "Failed to parse JSON config: " + jsonConfig, e); |
| | | Timber.e(e, "Failed to parse JSON config: %s", jsonConfig); |
| | | return 1; |
| | | } |
| | | |
| | |
| | | EncodeConfig config2 = EncodeConfig.fromJson(jsonConfig); |
| | | return startNetworkEncode(config2); |
| | | } catch (JSONException e) { |
| | | Log.e(TAG, "Failed to parse JSON config: " + jsonConfig, e); |
| | | Timber.e(e, "Failed to parse JSON config: %s", jsonConfig); |
| | | return 1; |
| | | } |
| | | |
| | |
| | | FileTransmitConfig config4 = FileTransmitConfig.fromJson(jsonConfig); |
| | | return startFileTransmit(config4); |
| | | } catch (JSONException e) { |
| | | Log.e(TAG, "Failed to parse JSON config: " + jsonConfig, e); |
| | | Timber.e(e, "Failed to parse JSON config: %s", jsonConfig); |
| | | return 1; |
| | | } |
| | | |
| | | case 5: // 停止H264文件传输 |
| | | Timber.i("客户端请求停止视频文件上传"); |
| | | return stopFileTransmitter(); |
| | | |
| | | default: |
| | | Log.e(TAG, "Unknown action: " + action); |
| | | Timber.e("Unknown action: %d", action); |
| | | return 1; // 失败 |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error in controlEncode", e); |
| | | Timber.e(e, "Error in controlEncode"); |
| | | return 1; // 失败 |
| | | } |
| | | } |
| | |
| | | * 启动文件编码模式(只写入文件,不进行网络推送) |
| | | */ |
| | | private int startFileEncode(EncodeConfig config) { |
| | | Log.d(TAG, "Starting file encode mode"); |
| | | Timber.d("Starting file encode mode"); |
| | | |
| | | // 如果编码器已经在运行,先停止 |
| | | if (h264Encoder != null) { |
| | | Log.w(TAG, "Encoder is already running, stopping it first"); |
| | | Timber.w("Encoder is already running, stopping it first"); |
| | | stopEncoder(); |
| | | } |
| | | |
| | |
| | | long timeFile = System.currentTimeMillis(); |
| | | SimpleDateFormat bcdFormat = new SimpleDateFormat("yyMMddHHmmss"); |
| | | String str = bcdFormat.format(timeFile); |
| | | Log.i(TAG,"文件名:"+str); |
| | | Timber.i("文件名:%s", str); |
| | | // 设置输出文件 |
| | | String fileName = "h264_" + timeFile+ ".h264"; |
| | | File outputFile = new File(outputFileDirectory, fileName); |
| | |
| | | // 初始化并启动(使用配置中的分辨率) |
| | | int[] resolution = {width, height}; |
| | | if (h264Encoder.initialize(DEFAULT_CAMERA_ID_RANGE, null, resolution, false)) { |
| | | // 应用已保存的水印信息(如果有) |
| | | if (currentWatermarkInfo != null) { |
| | | h264Encoder.setWatermarkInfo(currentWatermarkInfo); |
| | | Timber.d("Applied saved watermark info to encoder"); |
| | | } |
| | | h264Encoder.start(); |
| | | Log.d(TAG, "File encode started successfully, output file: " + outputFile.getAbsolutePath() + |
| | | ", resolution: " + width + "x" + height + ", framerate: " + framerate); |
| | | Timber.d("File encode started successfully, output file: %s, resolution: %dx%d, framerate: %d", |
| | | outputFile.getAbsolutePath(), width, height, framerate); |
| | | return 0; // 成功 |
| | | } else { |
| | | Log.e(TAG, "Failed to initialize encoder"); |
| | | Timber.e("Failed to initialize encoder"); |
| | | h264Encoder = null; |
| | | return 1; // 失败 |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Failed to start file encode", e); |
| | | Timber.e(e, "Failed to start file encode"); |
| | | h264Encoder = null; |
| | | return 1; // 失败 |
| | | } |
| | |
| | | * 启动网络推送模式(只进行网络推送,不写入文件) |
| | | */ |
| | | private int startNetworkEncode(EncodeConfig config) { |
| | | Log.d(TAG, "Starting network encode mode"); |
| | | Timber.d("Starting network encode mode"); |
| | | |
| | | // 如果编码器已经在运行,先停止 |
| | | if (h264Encoder != null) { |
| | | Log.w(TAG, "Encoder is already running, stopping it first"); |
| | | Timber.w("Encoder is already running, stopping it first"); |
| | | stopEncoder(); |
| | | } |
| | | |
| | | // 检查必需的配置参数 |
| | | if (config == null || config.ip == null || config.ip.trim().isEmpty() || config.port <= 0) { |
| | | Log.e(TAG, "Network encode requires valid ip and port in config"); |
| | | Timber.e("Network encode requires valid ip and port in config"); |
| | | return 1; // 失败 |
| | | } |
| | | |
| | |
| | | |
| | | |
| | | // 设置编码参数(使用配置中的参数) |
| | | int width = config != null ? config.width : DEFAULT_WIDTH; |
| | | int height = config != null ? config.height : DEFAULT_HEIGHT; |
| | | int framerate = config != null ? config.framerate : DEFAULT_FRAME_RATE; |
| | | int width = DEFAULT_WIDTH; |
| | | int height = DEFAULT_HEIGHT; |
| | | int framerate = DEFAULT_FRAME_RATE; |
| | | h264Encoder.setEncoderParams(width, height, framerate, DEFAULT_BITRATE); |
| | | |
| | | // 禁用文件输出 |
| | |
| | | // 初始化并启动(使用配置中的分辨率) |
| | | int[] resolution = {width, height}; |
| | | if (h264Encoder.initialize(DEFAULT_CAMERA_ID_RANGE, null, resolution, false)) { |
| | | // 应用已保存的水印信息(如果有) |
| | | if (currentWatermarkInfo != null) { |
| | | h264Encoder.setWatermarkInfo(currentWatermarkInfo); |
| | | Timber.d("Applied saved watermark info to encoder"); |
| | | } |
| | | h264Encoder.start(); |
| | | Log.d(TAG, "Network encode started successfully, server: " + config.ip + ":" + config.port + |
| | | ", resolution: " + width + "x" + height + ", framerate: " + framerate); |
| | | Timber.d("Network encode started successfully, server: %s:%d, resolution: %dx%d, framerate: %d", |
| | | config.ip, config.port, width, height, framerate); |
| | | return 0; // 成功 |
| | | } else { |
| | | Log.e(TAG, "Failed to initialize encoder"); |
| | | Timber.e("Failed to initialize encoder"); |
| | | h264Encoder = null; |
| | | return 1; // 失败 |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Failed to start network encode", e); |
| | | Timber.e(e, "Failed to start network encode"); |
| | | h264Encoder = null; |
| | | return 1; // 失败 |
| | | } |
| | |
| | | * 停止编码器 |
| | | */ |
| | | private int stopEncoder() { |
| | | Log.d(TAG, "Stopping encoder"); |
| | | Timber.d("Stopping encoder"); |
| | | |
| | | if (h264Encoder != null) { |
| | | try { |
| | | h264Encoder.stop(); |
| | | h264Encoder.release(); |
| | | h264Encoder = null; |
| | | Log.d(TAG, "Encoder stopped successfully"); |
| | | Timber.d("Encoder stopped successfully"); |
| | | return 0; // 成功 |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error stopping encoder", e); |
| | | Timber.e(e, "Error stopping encoder"); |
| | | h264Encoder = null; |
| | | return 1; // 失败 |
| | | } |
| | | } else { |
| | | Log.w(TAG, "Encoder is not running"); |
| | | Timber.w("Encoder is not running"); |
| | | return 0; // 成功(没有运行的编码器,视为成功) |
| | | } |
| | | } |
| | |
| | | * 启动文件传输模式(从H264文件读取并网络推送) |
| | | */ |
| | | private int startFileTransmit(FileTransmitConfig config) { |
| | | Log.d(TAG, "Starting file transmit mode"); |
| | | Timber.d("Starting file transmit mode"); |
| | | |
| | | // 如果文件传输器已经在运行,先停止 |
| | | if (h264FileTransmitter != null) { |
| | | Log.w(TAG, "File transmitter is already running, stopping it first"); |
| | | Timber.w("File transmitter is already running, stopping it first"); |
| | | stopFileTransmitter(); |
| | | } |
| | | |
| | | // 检查必需的配置参数 |
| | | if (config == null || config.ip == null || config.ip.trim().isEmpty() || config.port <= 0) { |
| | | Log.e(TAG, "File transmit requires valid ip and port in config"); |
| | | Timber.e("File transmit requires valid ip and port in config"); |
| | | return 1; // 失败 |
| | | } |
| | | |
| | | if (config.filePath == null || config.filePath.trim().isEmpty()) { |
| | | Log.e(TAG, "File transmit requires valid filePath in config"); |
| | | Timber.e("File transmit requires valid filePath in config"); |
| | | return 1; // 失败 |
| | | } |
| | | |
| | |
| | | // 检查文件是否存在 |
| | | File file = new File(config.filePath); |
| | | if (!file.exists() || !file.isFile()) { |
| | | Log.e(TAG, "File does not exist: " + config.filePath); |
| | | Timber.e("File does not exist: %s", config.filePath); |
| | | return 1; // 失败 |
| | | } |
| | | |
| | |
| | | h264FileTransmitter.setServerAddress(config.ip, config.port); |
| | | |
| | | // 设置协议类型 |
| | | h264FileTransmitter.setProtocolType(config.protocolType); |
| | | h264FileTransmitter.setProtocolType(1); //1-tcp |
| | | |
| | | // 设置协议参数(SIM卡号和逻辑通道号) |
| | | String simPhone = config.simPhone != null && !config.simPhone.trim().isEmpty() |
| | |
| | | h264FileTransmitter.setOnTransmitProgressCallback(new H264FileTransmitter.OnTransmitProgressCallback() { |
| | | @Override |
| | | public void onProgress(int currentFrame, int totalFrames) { |
| | | Log.d(TAG, "File transmit progress: frame " + currentFrame + |
| | | (totalFrames > 0 ? " of " + totalFrames : "")); |
| | | Timber.d("File transmit progress: frame %d%s", currentFrame, |
| | | totalFrames > 0 ? " of " + totalFrames : ""); |
| | | } |
| | | |
| | | @Override |
| | | public void onComplete() { |
| | | Log.d(TAG, "File transmit completed"); |
| | | Timber.d("File transmit completed"); |
| | | stopFileTransmitter(); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(String error) { |
| | | Log.e(TAG, "File transmit error: " + error); |
| | | Timber.e("File transmit error: %s", error); |
| | | } |
| | | }); |
| | | |
| | | // 初始化Socket连接 |
| | | if (!h264FileTransmitter.initialize()) { |
| | | Log.e(TAG, "Failed to initialize file transmitter socket"); |
| | | Timber.e("Failed to initialize file transmitter socket"); |
| | | h264FileTransmitter = null; |
| | | return 1; // 失败 |
| | | } |
| | |
| | | // 开始传输文件 |
| | | h264FileTransmitter.transmitFile(config.filePath); |
| | | |
| | | Log.d(TAG, "File transmit started successfully, file: " + config.filePath + |
| | | ", server: " + config.ip + ":" + config.port + |
| | | ", protocol: " + (config.protocolType == JT1076ProtocolHelper.PROTOCOL_TYPE_UDP ? "UDP" : "TCP") + |
| | | ", framerate: " + framerate); |
| | | Timber.d("File transmit started successfully, file: %s, server: %s:%d, protocol: %s, framerate: %d", |
| | | config.filePath, config.ip, config.port, |
| | | config.protocolType == JT1076ProtocolHelper.PROTOCOL_TYPE_UDP ? "UDP" : "TCP", framerate); |
| | | return 0; // 成功 |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Failed to start file transmit", e); |
| | | Timber.e(e, "Failed to start file transmit"); |
| | | if (h264FileTransmitter != null) { |
| | | try { |
| | | h264FileTransmitter.stop(); |
| | | } catch (Exception ex) { |
| | | Log.e(TAG, "Error stopping file transmitter after failure", ex); |
| | | Timber.e(ex, "Error stopping file transmitter after failure"); |
| | | } |
| | | h264FileTransmitter = null; |
| | | } |
| | |
| | | * 停止文件传输器 |
| | | */ |
| | | private int stopFileTransmitter() { |
| | | Log.d(TAG, "Stopping file transmitter"); |
| | | Timber.d("Stopping file transmitter"); |
| | | |
| | | if (h264FileTransmitter != null) { |
| | | try { |
| | | h264FileTransmitter.stop(); |
| | | h264FileTransmitter = null; |
| | | Log.d(TAG, "File transmitter stopped successfully"); |
| | | Timber.d("File transmitter stopped successfully"); |
| | | return 0; // 成功 |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error stopping file transmitter", e); |
| | | Timber.e(e, "Error stopping file transmitter"); |
| | | h264FileTransmitter = null; |
| | | return 1; // 失败 |
| | | } |
| | | } else { |
| | | Log.w(TAG, "File transmitter is not running"); |
| | | Timber.w("File transmitter is not running"); |
| | | return 0; // 成功(没有运行的文件传输器,视为成功) |
| | | } |
| | | } |
| | |
| | | * @return 资源列表 |
| | | */ |
| | | private List<ResourceInfo> getResourceList(String startTime, String endTime) { |
| | | Log.d(TAG, "getResourceList called, startTime: " + startTime + ", endTime: " + endTime); |
| | | Timber.d("getResourceList called, startTime: %s, endTime: %s", startTime, endTime); |
| | | |
| | | List<ResourceInfo> resourceList = new ArrayList<>(); |
| | | |
| | |
| | | // 扫描输出目录中的H264文件 |
| | | File dir = new File(outputFileDirectory); |
| | | if (!dir.exists() || !dir.isDirectory()) { |
| | | Log.w(TAG, "Output directory does not exist: " + outputFileDirectory); |
| | | Timber.w("Output directory does not exist: %s", outputFileDirectory); |
| | | return resourceList; |
| | | } |
| | | |
| | | File[] files = dir.listFiles((dir1, name) -> name.toLowerCase().endsWith(".h264")); |
| | | if (files == null || files.length == 0) { |
| | | Log.d(TAG, "No H264 files found in directory"); |
| | | Timber.d("No H264 files found in directory"); |
| | | return resourceList; |
| | | } |
| | | |
| | |
| | | Date endDate = parseTime(endTime); |
| | | |
| | | if (startDate == null || endDate == null) { |
| | | Log.e(TAG, "Invalid time format, startTime: " + startTime + ", endTime: " + endTime); |
| | | Timber.e("Invalid time format, startTime: %s, endTime: %s", startTime, endTime); |
| | | return resourceList; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | Log.d(TAG, "Found " + resourceList.size() + " resources in time range"); |
| | | Timber.d("Found %d resources in time range", resourceList.size()); |
| | | return resourceList; |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error getting resource list", e); |
| | | Timber.e(e, "Error getting resource list"); |
| | | return resourceList; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 设置水印信息 |
| | | * @param watermarkInfoJson 水印信息JSON字符串,包含:车牌(plateNumber)、学员(student)、教练(coach)、 |
| | | * 经度(longitude)、纬度(latitude)、驾校(drivingSchool)、车速(speed) |
| | | * 示例:{"plateNumber":"京A12345","student":"张三","coach":"李四", |
| | | * "longitude":116.397128,"latitude":39.916527,"drivingSchool":"XX驾校","speed":60.5} |
| | | */ |
| | | private void setWatermarkInfo(String watermarkInfoJson) { |
| | | Timber.d("setWatermarkInfo called, watermarkInfoJson: %s", watermarkInfoJson); |
| | | |
| | | try { |
| | | if (watermarkInfoJson == null || watermarkInfoJson.trim().isEmpty()) { |
| | | Timber.w("Watermark info JSON is null or empty, clearing watermark"); |
| | | currentWatermarkInfo = null; |
| | | // 如果编码器正在运行,清除水印 |
| | | if (h264Encoder != null) { |
| | | h264Encoder.setWatermarkInfo(null); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | // 解析JSON |
| | | JSONObject json = new JSONObject(watermarkInfoJson); |
| | | WatermarkInfo watermarkInfo = new WatermarkInfo(); |
| | | |
| | | // 解析各个字段(使用 optString/optDouble 避免字段不存在时抛出异常) |
| | | watermarkInfo.setPlateNumber(json.optString("plateNumber", null)); |
| | | watermarkInfo.setStudent(json.optString("student", null)); |
| | | watermarkInfo.setCoach(json.optString("coach", null)); |
| | | |
| | | // 经度和纬度可能是数字或字符串 |
| | | if (json.has("longitude")) { |
| | | Object lonObj = json.get("longitude"); |
| | | if (lonObj instanceof Number) { |
| | | watermarkInfo.setLongitude(((Number) lonObj).doubleValue()); |
| | | } else if (lonObj instanceof String) { |
| | | try { |
| | | watermarkInfo.setLongitude(Double.parseDouble((String) lonObj)); |
| | | } catch (NumberFormatException e) { |
| | | Timber.w("Invalid longitude format: %s", lonObj); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (json.has("latitude")) { |
| | | Object latObj = json.get("latitude"); |
| | | if (latObj instanceof Number) { |
| | | watermarkInfo.setLatitude(((Number) latObj).doubleValue()); |
| | | } else if (latObj instanceof String) { |
| | | try { |
| | | watermarkInfo.setLatitude(Double.parseDouble((String) latObj)); |
| | | } catch (NumberFormatException e) { |
| | | Timber.w("Invalid latitude format: %s", latObj); |
| | | } |
| | | } |
| | | } |
| | | |
| | | watermarkInfo.setDrivingSchool(json.optString("drivingSchool", null)); |
| | | |
| | | // 车速可能是数字或字符串 |
| | | if (json.has("speed")) { |
| | | Object speedObj = json.get("speed"); |
| | | if (speedObj instanceof Number) { |
| | | watermarkInfo.setSpeed(((Number) speedObj).doubleValue()); |
| | | } else if (speedObj instanceof String) { |
| | | try { |
| | | watermarkInfo.setSpeed(Double.parseDouble((String) speedObj)); |
| | | } catch (NumberFormatException e) { |
| | | Timber.w("Invalid speed format: %s", speedObj); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 保存水印信息 |
| | | currentWatermarkInfo = watermarkInfo; |
| | | Timber.i("Watermark info parsed successfully: %s", watermarkInfo); |
| | | |
| | | // 如果编码器正在运行,立即应用水印 |
| | | if (h264Encoder != null) { |
| | | h264Encoder.setWatermarkInfo(watermarkInfo); |
| | | Timber.d("Watermark applied to encoder"); |
| | | } else { |
| | | Timber.d("Encoder not running, watermark will be applied when encoder starts"); |
| | | } |
| | | |
| | | } catch (JSONException e) { |
| | | Timber.e(e, "Failed to parse watermark info JSON: %s", watermarkInfoJson); |
| | | currentWatermarkInfo = null; |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Unexpected error setting watermark info"); |
| | | currentWatermarkInfo = null; |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | private ResourceInfo createResourceInfoFromFile(File file, Date startDate, Date endDate) { |
| | | try { |
| | | // 从文件名或文件修改时间获取文件时间 |
| | | // 这里假设文件名包含时间戳,或者使用文件修改时间 |
| | | Date fileDate = new Date(file.lastModified()); |
| | | // 从文件名中提取时间戳(格式:h264_1234567890123.h264) |
| | | String fileName = file.getName(); |
| | | Date startTimeFromFileName = null; |
| | | |
| | | if (fileName.startsWith("h264_") && fileName.endsWith(".h264")) { |
| | | try { |
| | | // 提取文件名中的时间戳(h264_ 和 .h264 之间的部分) |
| | | String timestampStr = fileName.substring(5, fileName.length() - 5); // 去掉 "h264_" 和 ".h264" |
| | | long timestamp = Long.parseLong(timestampStr); |
| | | startTimeFromFileName = new Date(timestamp); |
| | | } catch (NumberFormatException e) { |
| | | Timber.w("Failed to parse timestamp from filename: %s", fileName); |
| | | } |
| | | } |
| | | |
| | | // 如果无法从文件名解析时间戳,则使用文件修改时间作为开始时间 |
| | | if (startTimeFromFileName == null) { |
| | | startTimeFromFileName = new Date(file.lastModified()); |
| | | } |
| | | |
| | | // 结束时间使用文件修改时间 |
| | | Date endTimeFromFile = new Date(file.lastModified()); |
| | | |
| | | // 检查文件时间是否在指定范围内 |
| | | if (fileDate.before(startDate) || fileDate.after(endDate)) { |
| | | // 开始时间应该 >= startDate,结束时间应该 <= endDate |
| | | // 如果文件的开始时间在范围内,或者结束时间在范围内,或者文件时间范围包含查询范围,则包含该文件 |
| | | if (startTimeFromFileName.after(endDate) || endTimeFromFile.before(startDate)) { |
| | | return null; // 不在时间范围内 |
| | | } |
| | | |
| | |
| | | // 逻辑通道号(默认值,实际应从配置获取) |
| | | resourceInfo.setLogicalChannelNumber((byte) 1); |
| | | |
| | | // 开始时间和结束时间(使用文件修改时间) |
| | | // 开始时间:从文件名中的时间戳 |
| | | SimpleDateFormat bcdFormat = new SimpleDateFormat("yyMMddHHmmss", Locale.CHINA); |
| | | resourceInfo.setStartTime(bcdFormat.format(fileDate)); |
| | | // 结束时间可以使用文件修改时间加上一个默认时长(例如1分钟) |
| | | long fileDuration = 60000; // 默认1分钟,实际应该根据文件内容计算 |
| | | Date endTime = new Date(fileDate.getTime() + fileDuration); |
| | | resourceInfo.setEndTime(bcdFormat.format(endTime)); |
| | | resourceInfo.setStartTime(bcdFormat.format(startTimeFromFileName)); |
| | | |
| | | // 结束时间:使用文件修改时间 |
| | | resourceInfo.setEndTime(bcdFormat.format(endTimeFromFile)); |
| | | |
| | | // 报警标志(默认值,实际应从文件元数据获取) |
| | | resourceInfo.setAlarmFlag(0L); |
| | |
| | | return resourceInfo; |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error creating resource info from file: " + file.getName(), e); |
| | | Timber.e(e, "Error creating resource info from file: %s", file.getName()); |
| | | return null; |
| | | } |
| | | } |
| | |
| | | SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmss", Locale.CHINA); |
| | | return format.parse(timeStr); |
| | | } catch (ParseException e) { |
| | | Log.e(TAG, "Failed to parse time: " + timeStr, e); |
| | | Timber.e(e, "Failed to parse time: %s", timeStr); |
| | | return null; |
| | | } |
| | | } |
| | |
| | | import android.media.MediaCodec; |
| | | import android.media.MediaCodecInfo; |
| | | import android.media.MediaFormat; |
| | | import android.util.Log; |
| | | import com.anyun.libusbcamera.UsbCamera; |
| | | import com.anyun.libusbcamera.WatermarkParam; |
| | | import com.anyun.h264.model.WatermarkInfo; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileOutputStream; |
| | | import java.io.IOException; |
| | | import java.nio.ByteBuffer; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | import timber.log.Timber; |
| | | |
| | | /** |
| | | * H264视频编码器 |
| | |
| | | */ |
| | | public void setEnableNetworkTransmission(boolean enable) { |
| | | this.enableNetworkTransmission = enable; |
| | | Log.d(TAG, "Network transmission " + (enable ? "enabled" : "disabled")); |
| | | Timber.d("Network transmission " + (enable ? "enabled" : "disabled")); |
| | | } |
| | | |
| | | /** |
| | | * 设置水印信息 |
| | | * @param watermarkInfo 水印信息对象 |
| | | */ |
| | | public void setWatermarkInfo(WatermarkInfo watermarkInfo) { |
| | | if (watermarkInfo == null) { |
| | | Timber.w("WatermarkInfo is null, disabling watermark"); |
| | | usbCamera.enableWatermark(false, null); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 构建水印文本列表(分行显示,每行一个信息项) |
| | | ArrayList<WatermarkParam> watermarkParams = new ArrayList<>(); |
| | | |
| | | // 从左上角开始,每行间隔25像素 |
| | | int yOffset = 30; |
| | | int xOffset = 10; |
| | | |
| | | // 车牌号 |
| | | if (watermarkInfo.getPlateNumber() != null && !watermarkInfo.getPlateNumber().isEmpty()) { |
| | | watermarkParams.add(new WatermarkParam(xOffset, yOffset, |
| | | "车牌:" + watermarkInfo.getPlateNumber())); |
| | | yOffset += 25; |
| | | } |
| | | |
| | | // 学员姓名 |
| | | if (watermarkInfo.getStudent() != null && !watermarkInfo.getStudent().isEmpty()) { |
| | | watermarkParams.add(new WatermarkParam(xOffset, yOffset, |
| | | "学员:" + watermarkInfo.getStudent())); |
| | | yOffset += 25; |
| | | } |
| | | |
| | | // 教练姓名 |
| | | if (watermarkInfo.getCoach() != null && !watermarkInfo.getCoach().isEmpty()) { |
| | | watermarkParams.add(new WatermarkParam(xOffset, yOffset, |
| | | "教练:" + watermarkInfo.getCoach())); |
| | | yOffset += 25; |
| | | } |
| | | |
| | | // 位置信息(纬度,经度) |
| | | if (watermarkInfo.getLongitude() != null && watermarkInfo.getLatitude() != null) { |
| | | watermarkParams.add(new WatermarkParam(xOffset, yOffset, |
| | | String.format("位置:%.6f,%.6f", watermarkInfo.getLatitude(), watermarkInfo.getLongitude()))); |
| | | yOffset += 25; |
| | | } |
| | | |
| | | // 驾校名称 |
| | | if (watermarkInfo.getDrivingSchool() != null && !watermarkInfo.getDrivingSchool().isEmpty()) { |
| | | watermarkParams.add(new WatermarkParam(xOffset, yOffset, |
| | | "驾校:" + watermarkInfo.getDrivingSchool())); |
| | | yOffset += 25; |
| | | } |
| | | |
| | | // 车速 |
| | | if (watermarkInfo.getSpeed() != null) { |
| | | watermarkParams.add(new WatermarkParam(xOffset, yOffset, |
| | | String.format("车速:%.1fkm/h", watermarkInfo.getSpeed()))); |
| | | } |
| | | |
| | | if (!watermarkParams.isEmpty()) { |
| | | // 启用水印,使用默认字体路径(如果系统有字体文件) |
| | | // 颜色:0-REVERSE(反色),1-BLACK,2-WHITE,3-RED,4-GREEN,5-BLUE |
| | | // 字体大小、倍数可以根据需要调整 |
| | | String fontPath = "/system/fonts/DroidSans.ttf"; // 默认字体路径,如果不存在可以传null |
| | | usbCamera.enableWatermark(true, fontPath); |
| | | |
| | | // 设置水印:颜色(2=白色),字体大小(24),倍数(1),文本列表 |
| | | usbCamera.setWatermark(2, 24, 1, watermarkParams); |
| | | |
| | | Timber.d("Watermark set successfully: %s", watermarkInfo); |
| | | } else { |
| | | Timber.w("No watermark text to display, disabling watermark"); |
| | | usbCamera.enableWatermark(false, null); |
| | | } |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Failed to set watermark"); |
| | | usbCamera.enableWatermark(false, null); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | if (result == 0) { |
| | | // 成功,跳出循环 |
| | | if (attempt > 0) { |
| | | Log.d(TAG, "prepareCamera succeeded on attempt " + (attempt + 1)); |
| | | Timber.d("prepareCamera succeeded on attempt " + (attempt + 1)); |
| | | } |
| | | break; |
| | | } else { |
| | | // 失败,记录日志 |
| | | Log.w(TAG, "prepareCamera failed on attempt " + (attempt + 1) + ": " + result); |
| | | Timber.w( "prepareCamera failed on attempt " + (attempt + 1) + ": " + result); |
| | | if (attempt < maxRetries - 1) { |
| | | Log.d(TAG, "Retrying prepareCamera..."); |
| | | Timber.d( "Retrying prepareCamera..."); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (result != 0) { |
| | | Log.e(TAG, "prepareCamera failed after " + maxRetries + " attempts: " + result); |
| | | Timber.e("prepareCamera failed after " + maxRetries + " attempts: " + result); |
| | | return false; |
| | | } |
| | | |
| | | // 更新实际分辨率 |
| | | width = actualResolution[0]; |
| | | height = actualResolution[1]; |
| | | Log.d(TAG, "Camera initialized with resolution: " + width + "x" + height); |
| | | Timber.d("Camera initialized with resolution: " + width + "x" + height); |
| | | |
| | | // 3. 初始化H264编码器 |
| | | initEncoder(); |
| | |
| | | return false; |
| | | } |
| | | } else { |
| | | Log.d(TAG, "Network transmission disabled, skipping socket initialization"); |
| | | Timber.d("Network transmission disabled, skipping socket initialization"); |
| | | } |
| | | |
| | | // 5. 初始化文件输出(仅创建文件,SPS/PPS在第一次输出时写入) |
| | | if (enableFileOutput && outputFilePath != null && !outputFilePath.isEmpty()) { |
| | | if (!initFileOutput()) { |
| | | Log.w(TAG, "File output initialization failed, continuing without file output"); |
| | | Timber.w("File output initialization failed, continuing without file output"); |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Initialize failed", e); |
| | | Timber.e(e,"Initialize failed"); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); |
| | | encoder.start(); |
| | | |
| | | Log.d(TAG, "H264 encoder initialized"); |
| | | Timber.d( "H264 encoder initialized"); |
| | | } |
| | | |
| | | /** |
| | |
| | | if (parentDir != null && !parentDir.exists()) { |
| | | boolean created = parentDir.mkdirs(); |
| | | if (!created && !parentDir.exists()) { |
| | | Log.e(TAG, "Failed to create parent directory: " + parentDir.getAbsolutePath()); |
| | | Timber.e("Failed to create parent directory: " + parentDir.getAbsolutePath()); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | fileOutputStream = new FileOutputStream(file); |
| | | spsPpsWritten = false; |
| | | |
| | | Log.d(TAG, "File output initialized: " + outputFilePath); |
| | | Timber.d("File output initialized: " + outputFilePath); |
| | | return true; |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Initialize file output failed", e); |
| | | Timber.e(e,"Initialize file output failed"); |
| | | if (fileOutputStream != null) { |
| | | try { |
| | | fileOutputStream.close(); |
| | | } catch (IOException ie) { |
| | | Log.e(TAG, "Close file output stream failed", ie); |
| | | Timber.e(ie, "Close file output stream failed"); |
| | | } |
| | | fileOutputStream = null; |
| | | } |
| | |
| | | fileOutputStream.flush(); |
| | | |
| | | spsPpsWritten = true; |
| | | Log.d(TAG, "SPS/PPS written to file, SPS size: " + spsLength + ", PPS size: " + ppsLength); |
| | | Timber.d("SPS/PPS written to file, SPS size: " + spsLength + ", PPS size: " + ppsLength); |
| | | } else { |
| | | Log.w(TAG, "SPS/PPS not found in CSD, will extract from first key frame"); |
| | | Timber.w("SPS/PPS not found in CSD, will extract from first key frame"); |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Write SPS/PPS to file error", e); |
| | | Timber.e(e,"Write SPS/PPS to file error"); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | public void start() { |
| | | if (isRunning.get()) { |
| | | Log.w(TAG, "Encoder is already running"); |
| | | Timber.w("Encoder is already running"); |
| | | return; |
| | | } |
| | | |
| | |
| | | }); |
| | | encodeThread.start(); |
| | | |
| | | Log.d(TAG, "H264 encoder started"); |
| | | Timber.d("H264 encoder started"); |
| | | } |
| | | |
| | | /** |
| | |
| | | // processCamera - 读取一帧 |
| | | int processResult = usbCamera.processCamera(); |
| | | if (processResult != 0) { |
| | | Log.w(TAG, "processCamera returned: " + processResult); |
| | | Timber.w("processCamera returned: " + processResult); |
| | | Thread.sleep(10); |
| | | continue; |
| | | } |
| | |
| | | encodeFrame(nv12ToNV21(yuvBuffer,width,height), timestamp, bufferInfo); |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Encode loop error", e); |
| | | Timber.e(e, "Encode loop error"); |
| | | try { |
| | | Thread.sleep(100); |
| | | } catch (InterruptedException ie) { |
| | |
| | | } |
| | | } |
| | | |
| | | Log.d(TAG, "Encode loop exited"); |
| | | Timber.d( "Encode loop exited"); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Encode frame error", e); |
| | | Timber.e(e,"Encode frame error"); |
| | | } |
| | | } |
| | | |
| | |
| | | // MediaCodec输出的关键帧通常已经包含SPS/PPS,但为了确保文件完整性, |
| | | // 我们已经从CSD写入,这里直接写入关键帧数据即可 |
| | | if (!spsPpsWritten) { |
| | | Log.d(TAG, "SPS/PPS will be included in key frame data"); |
| | | Timber.d("SPS/PPS will be included in key frame data"); |
| | | } |
| | | } |
| | | |
| | |
| | | fileOutputStream.write(data); |
| | | fileOutputStream.flush(); |
| | | } catch (IOException e) { |
| | | Log.e(TAG, "Write to file error", e); |
| | | Timber.e(e, "Write to file error"); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Send encoded data error", e); |
| | | Timber.e(e,"Send encoded data error"); |
| | | } |
| | | } |
| | | |
| | |
| | | try { |
| | | encodeThread.join(2000); |
| | | } catch (InterruptedException e) { |
| | | Log.e(TAG, "Wait encode thread error", e); |
| | | Timber.e(e, "Wait encode thread error"); |
| | | } |
| | | } |
| | | |
| | |
| | | encoder.release(); |
| | | encoder = null; |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Release encoder error", e); |
| | | Timber.e(e, "Release encoder error"); |
| | | } |
| | | } |
| | | |
| | |
| | | // 关闭文件输出 |
| | | closeFileOutput(); |
| | | |
| | | Log.d(TAG, "H264 encoder stopped"); |
| | | Timber.d("H264 encoder stopped"); |
| | | } |
| | | |
| | | /** |
| | |
| | | try { |
| | | fileOutputStream.flush(); |
| | | fileOutputStream.close(); |
| | | Log.d(TAG, "File output closed: " + outputFilePath); |
| | | Timber.d("File output closed: " + outputFilePath); |
| | | } catch (IOException e) { |
| | | Log.e(TAG, "Close file output error", e); |
| | | Timber.e(e, "Close file output error"); |
| | | } finally { |
| | | fileOutputStream = null; |
| | | spsPpsWritten = false; |
| | |
| | | package com.anyun.h264; |
| | | |
| | | import android.util.Log; |
| | | import timber.log.Timber; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | |
| | | * @param protocolType PROTOCOL_TYPE_UDP 或 PROTOCOL_TYPE_TCP |
| | | */ |
| | | public void setProtocolType(int protocolType) { |
| | | protocolHelper.setProtocolType(protocolType); |
| | | protocolHelper.setProtocolType(JT1076ProtocolHelper.PROTOCOL_TYPE_TCP); |
| | | } |
| | | |
| | | /** |
| | |
| | | public void setFrameRate(int frameRate) { |
| | | this.frameRate = frameRate > 0 ? frameRate : 25; |
| | | this.frameInterval = 1000 / this.frameRate; |
| | | Log.d(TAG, "Frame rate set to: " + this.frameRate + " fps, interval: " + this.frameInterval + " ms"); |
| | | Timber.d("Frame rate set to: %d fps, interval: %d ms", this.frameRate, this.frameInterval); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public boolean initialize() { |
| | | if (isRunning.get()) { |
| | | Log.w(TAG, "Transmitter is already running"); |
| | | Timber.w("Transmitter is already running"); |
| | | return false; |
| | | } |
| | | |
| | | if (!protocolHelper.initializeSocket()) { |
| | | Log.e(TAG, "Failed to initialize socket"); |
| | | Timber.e("Failed to initialize socket"); |
| | | return false; |
| | | } |
| | | |
| | |
| | | lastIFrameTime = 0; |
| | | lastFrameTime = 0; |
| | | |
| | | Log.d(TAG, "Socket initialized successfully"); |
| | | Timber.d("Socket initialized successfully"); |
| | | return true; |
| | | } |
| | | |
| | |
| | | */ |
| | | public void transmitFile(String filePath) { |
| | | if (isRunning.get()) { |
| | | Log.w(TAG, "Transmitter is already running"); |
| | | Timber.w("Transmitter is already running"); |
| | | return; |
| | | } |
| | | |
| | | File file = new File(filePath); |
| | | if (!file.exists() || !file.isFile()) { |
| | | Log.e(TAG, "File does not exist: " + filePath); |
| | | Timber.e("File does not exist: %s", filePath); |
| | | if (progressCallback != null) { |
| | | progressCallback.onError("File does not exist: " + filePath); |
| | | } |
| | |
| | | }); |
| | | transmitThread.start(); |
| | | |
| | | Log.d(TAG, "Started transmitting file: " + filePath); |
| | | Timber.d("Started transmitting file: %s", filePath); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | if (totalRead != fileData.length) { |
| | | Log.w(TAG, "File read incomplete, expected: " + fileData.length + ", actual: " + totalRead); |
| | | Timber.w("File read incomplete, expected: %d, actual: %d", fileData.length, totalRead); |
| | | } |
| | | |
| | | Log.d(TAG, "File read complete, size: " + fileData.length + " bytes"); |
| | | Timber.d("File read complete, size: %d bytes", fileData.length); |
| | | |
| | | // 按帧解析并传输(一个帧包含从一个起始码到下一个起始码之间的所有数据,包括起始码) |
| | | int offset = 0; |
| | |
| | | offset = nextFrameStart; |
| | | } |
| | | |
| | | Log.d(TAG, "Transmission complete, total frames: " + frameCount); |
| | | Timber.d("Transmission complete, total frames: %d", frameCount); |
| | | |
| | | if (progressCallback != null) { |
| | | progressCallback.onComplete(); |
| | | } |
| | | |
| | | } catch (IOException e) { |
| | | Log.e(TAG, "Error transmitting file", e); |
| | | Timber.e(e, "Error transmitting file"); |
| | | if (progressCallback != null) { |
| | | progressCallback.onError("IO Error: " + e.getMessage()); |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Unexpected error during transmission", e); |
| | | Timber.e(e, "Unexpected error during transmission"); |
| | | if (progressCallback != null) { |
| | | progressCallback.onError("Error: " + e.getMessage()); |
| | | } |
| | |
| | | try { |
| | | fis.close(); |
| | | } catch (IOException e) { |
| | | Log.e(TAG, "Error closing file", e); |
| | | Timber.e(e, "Error closing file"); |
| | | } |
| | | } |
| | | isRunning.set(false); |
| | |
| | | Thread.sleep(frameInterval); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | Log.d(TAG, "Transmission interrupted"); |
| | | Timber.d("Transmission interrupted"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error transmitting frame", e); |
| | | Timber.e(e, "Error transmitting frame"); |
| | | } |
| | | } |
| | | |
| | |
| | | try { |
| | | transmitThread.join(2000); |
| | | } catch (InterruptedException e) { |
| | | Log.e(TAG, "Wait transmit thread error", e); |
| | | Timber.e(e, "Wait transmit thread error"); |
| | | } |
| | | } |
| | | |
| | |
| | | protocolHelper.closeSocket(); |
| | | } |
| | | |
| | | Log.d(TAG, "H264 file transmitter stopped"); |
| | | Timber.d("H264 file transmitter stopped"); |
| | | } |
| | | |
| | | /** |
| | |
| | | package com.anyun.h264; |
| | | |
| | | import android.util.Log; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.DatagramPacket; |
| | | import java.net.DatagramSocket; |
| | | import java.net.InetAddress; |
| | | import java.nio.ByteBuffer; |
| | | |
| | | import timber.log.Timber; |
| | | |
| | | /** |
| | | * JT/T 1076-2016 协议工具类 |
| | |
| | | private int serverPort; |
| | | |
| | | // 协议类型(默认UDP) |
| | | private int protocolType = PROTOCOL_TYPE_UDP; |
| | | private int protocolType = PROTOCOL_TYPE_TCP; |
| | | |
| | | // UDP参数 |
| | | private DatagramSocket udpSocket; |
| | |
| | | */ |
| | | public void setProtocolType(int protocolType) { |
| | | if (protocolType != PROTOCOL_TYPE_UDP && protocolType != PROTOCOL_TYPE_TCP) { |
| | | Log.w(TAG, "Invalid protocol type: " + protocolType + ", using UDP"); |
| | | protocolType = PROTOCOL_TYPE_UDP; |
| | | Timber.w("Invalid protocol type: " + protocolType + ", using UDP"); |
| | | protocolType = PROTOCOL_TYPE_TCP; |
| | | } |
| | | |
| | | // 如果协议类型改变,先关闭旧的连接 |
| | |
| | | } |
| | | |
| | | this.protocolType = protocolType; |
| | | Log.d(TAG, "Protocol type set to: " + (protocolType == PROTOCOL_TYPE_UDP ? "UDP" : "TCP")); |
| | | Timber.d("Protocol type set to: " + (protocolType == PROTOCOL_TYPE_UDP ? "UDP" : "TCP")); |
| | | } |
| | | |
| | | /** |
| | |
| | | public boolean initializeUdpSocket() { |
| | | try { |
| | | if (serverIp == null || serverIp.isEmpty()) { |
| | | Log.e(TAG, "Server IP not set"); |
| | | Timber.e("Server IP not set"); |
| | | return false; |
| | | } |
| | | |
| | | udpSocket = new DatagramSocket(); |
| | | serverAddress = InetAddress.getByName(serverIp); |
| | | Log.d(TAG, "UDP socket initialized, target: " + serverIp + ":" + serverPort); |
| | | Timber.d("UDP socket initialized, target: " + serverIp + ":" + serverPort); |
| | | return true; |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Initialize UDP socket failed", e); |
| | | Timber.e(e,"Initialize UDP socket failed"); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | public boolean initializeTcpSocket() { |
| | | try { |
| | | if (serverIp == null || serverIp.isEmpty()) { |
| | | Log.e(TAG, "Server IP not set"); |
| | | Timber.e("Server IP not set"); |
| | | return false; |
| | | } |
| | | |
| | |
| | | tcpClient.setConnectionListener(new JT1076TcpClient.ConnectionListener() { |
| | | @Override |
| | | public void onConnected() { |
| | | Log.d(TAG, "TCP connection established"); |
| | | Timber.d("TCP connection established"); |
| | | } |
| | | |
| | | @Override |
| | | public void onDisconnected() { |
| | | Log.d(TAG, "TCP connection disconnected"); |
| | | Timber.d( "TCP connection disconnected"); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Throwable cause) { |
| | | Log.e(TAG, "TCP connection error", cause); |
| | | Timber.e(cause, "TCP connection error"); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | tcpClient.connect(); |
| | | Log.d(TAG, "TCP socket initializing, target: " + serverIp + ":" + serverPort); |
| | | Timber.d("TCP socket initializing, target: " + serverIp + ":" + serverPort); |
| | | return true; |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Initialize TCP socket failed", e); |
| | | Timber.e(e,"Initialize TCP socket failed"); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | try { |
| | | udpSocket.close(); |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Close UDP socket error", e); |
| | | Timber.e( e,"Close UDP socket error"); |
| | | } |
| | | udpSocket = null; |
| | | } |
| | |
| | | packet, packet.length, serverAddress, serverPort); |
| | | udpSocket.send(datagramPacket); |
| | | } else { |
| | | Log.w(TAG, "UDP socket not initialized"); |
| | | Timber.w("UDP socket not initialized"); |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Send UDP packet error", e); |
| | | Timber.e(e,"Send UDP packet error"); |
| | | } |
| | | } |
| | | |
| | |
| | | if (tcpClient != null && tcpClient.isConnected()) { |
| | | tcpClient.sendPacket(packet); |
| | | } else { |
| | | Log.w(TAG, "TCP socket not connected"); |
| | | Timber.w("TCP socket not connected"); |
| | | } |
| | | } |
| | | |
| | |
| | | package com.anyun.h264; |
| | | |
| | | import android.util.Log; |
| | | |
| | | import com.anyun.h264.util.BytesUtils; |
| | | |
| | |
| | | import io.netty.channel.ChannelInboundHandlerAdapter; |
| | | import io.netty.channel.ChannelInitializer; |
| | | import io.netty.channel.ChannelOption; |
| | | import io.netty.channel.ChannelPromise; |
| | | import io.netty.channel.EventLoopGroup; |
| | | import io.netty.channel.nio.NioEventLoopGroup; |
| | | import io.netty.channel.socket.SocketChannel; |
| | | import io.netty.channel.socket.nio.NioSocketChannel; |
| | | import timber.log.Timber; |
| | | |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | |
| | | */ |
| | | public void connect() { |
| | | if (serverIp == null || serverIp.isEmpty()) { |
| | | Log.e(TAG, "Server IP not set"); |
| | | Timber.e( "Server IP not set"); |
| | | if (connectionListener != null) { |
| | | connectionListener.onError(new IllegalArgumentException("Server IP not set")); |
| | | } |
| | |
| | | } |
| | | |
| | | if (workerGroup != null) { |
| | | Log.w(TAG, "TCP client already initialized, disconnecting first"); |
| | | Timber.w("TCP client already initialized, disconnecting first"); |
| | | disconnect(); |
| | | } |
| | | |
| | |
| | | } |
| | | }); |
| | | |
| | | Log.d(TAG, "Connecting to TCP server: " + serverIp + ":" + serverPort); |
| | | Timber.d("Connecting to TCP server: " + serverIp + ":" + serverPort); |
| | | |
| | | // 异步连接 |
| | | ChannelFuture future = bootstrap.connect(serverIp, serverPort); |
| | |
| | | if (future.isSuccess()) { |
| | | channel = future.channel(); |
| | | isConnected = true; |
| | | Log.d(TAG, "TCP connection established: " + serverIp + ":" + serverPort); |
| | | Timber.d("TCP connection established: " + serverIp + ":" + serverPort); |
| | | if (connectionListener != null) { |
| | | connectionListener.onConnected(); |
| | | } |
| | | } else { |
| | | isConnected = false; |
| | | Throwable cause = future.cause(); |
| | | Log.e(TAG, "TCP connection failed: " + serverIp + ":" + serverPort, cause); |
| | | Timber.e(cause, "TCP connection failed: " + serverIp + ":" + serverPort); |
| | | if (connectionListener != null) { |
| | | connectionListener.onError(cause); |
| | | } |
| | |
| | | }); |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Initialize TCP client failed", e); |
| | | Timber.e(e, "Initialize TCP client failed"); |
| | | isConnected = false; |
| | | if (connectionListener != null) { |
| | | connectionListener.onError(e); |
| | |
| | | */ |
| | | public void sendPacket(byte[] packet) { |
| | | if (!isConnected || channel == null || !channel.isActive()) { |
| | | Log.w(TAG, "TCP channel not connected, packet dropped"); |
| | | Timber.w( "TCP channel not connected, packet dropped"); |
| | | return; |
| | | } |
| | | |
| | |
| | | // 将字节数组包装为ByteBuf |
| | | ByteBuf buffer = Unpooled.wrappedBuffer(packet); |
| | | String str = BytesUtils.bytesToHexString( BytesUtils.subArray(buffer.array(),0,30)); |
| | | Log.i(TAG, "Send TCP packet:"+ str); |
| | | Timber.i( "Send TCP packet:"+ str); |
| | | // 异步写入 |
| | | ChannelFuture future = channel.writeAndFlush(buffer); |
| | | |
| | |
| | | @Override |
| | | public void operationComplete(ChannelFuture future) throws Exception { |
| | | if (!future.isSuccess()) { |
| | | Log.e(TAG, "Send TCP packet failed", future.cause()); |
| | | Timber.e(future.cause(), "Send TCP packet failed"); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Send TCP packet error", e); |
| | | Timber.e(e, "Send TCP packet error"); |
| | | } |
| | | } |
| | | |
| | |
| | | ChannelFuture future = channel.close(); |
| | | future.await(2, TimeUnit.SECONDS); |
| | | } catch (InterruptedException e) { |
| | | Log.w(TAG, "Close channel interrupted", e); |
| | | Timber.w(e,"Close channel interrupted"); |
| | | Thread.currentThread().interrupt(); |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Close channel error", e); |
| | | Timber.e(e, "Close channel error"); |
| | | } |
| | | channel = null; |
| | | } |
| | |
| | | connectionListener.onDisconnected(); |
| | | } |
| | | |
| | | Log.d(TAG, "TCP connection closed"); |
| | | Timber.d("TCP connection closed"); |
| | | } |
| | | |
| | | /** |
| | |
| | | try { |
| | | workerGroup.shutdownGracefully().await(3, TimeUnit.SECONDS); |
| | | } catch (InterruptedException e) { |
| | | Log.w(TAG, "Shutdown worker group interrupted", e); |
| | | Timber.w(e, "Shutdown worker group interrupted"); |
| | | Thread.currentThread().interrupt(); |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Shutdown worker group error", e); |
| | | Timber.e(e, "Shutdown worker group error"); |
| | | } |
| | | workerGroup = null; |
| | | } |
| | |
| | | @Override |
| | | public void channelActive(ChannelHandlerContext ctx) throws Exception { |
| | | super.channelActive(ctx); |
| | | Log.d(TAG, "TCP channel active: " + ctx.channel().remoteAddress()); |
| | | Timber.d("TCP channel active: " + ctx.channel().remoteAddress()); |
| | | isConnected = true; |
| | | } |
| | | |
| | | @Override |
| | | public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
| | | super.channelInactive(ctx); |
| | | Log.d(TAG, "TCP channel inactive: " + ctx.channel().remoteAddress()); |
| | | Timber.d("TCP channel inactive: " + ctx.channel().remoteAddress()); |
| | | isConnected = false; |
| | | channel = null; |
| | | if (connectionListener != null) { |
| | |
| | | |
| | | @Override |
| | | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
| | | Log.e(TAG, "TCP channel exception", cause); |
| | | Timber.e(cause, "TCP channel exception"); |
| | | isConnected = false; |
| | | if (connectionListener != null) { |
| | | connectionListener.onError(cause); |
| | |
| | | // 如果服务器有响应,可以在这里处理 |
| | | // 对于JT/T 1076-2016 RTP发送,通常不需要处理响应 |
| | | ByteBuf buf = (ByteBuf) msg; |
| | | Log.d(TAG, "Received data from server: " + buf.readableBytes() + " bytes"); |
| | | Timber.d("Received data from server: " + buf.readableBytes() + " bytes"); |
| | | buf.release(); // 释放ByteBuf |
| | | } |
| | | } |
| | |
| | | package com.anyun.h264 |
| | | |
| | | import android.os.Bundle |
| | | import android.util.Log |
| | | import timber.log.Timber |
| | | import androidx.activity.ComponentActivity |
| | | import androidx.activity.compose.setContent |
| | | import androidx.activity.enableEdgeToEdge |
| | |
| | | |
| | | private fun startH264Encoder(): Boolean { |
| | | if (h264Encoder != null) { |
| | | Log.w("MainActivity", "H264Encoder is already running") |
| | | Timber.w("H264Encoder is already running") |
| | | return false |
| | | } |
| | | |
| | |
| | | |
| | | if (h264Encoder?.initialize(cameraIdRange, null, resolution, false) == true) { |
| | | h264Encoder?.start() |
| | | Log.d("MainActivity", "H264Encoder started successfully") |
| | | Log.d("MainActivity", "Output file: ${outputFile.absolutePath}") |
| | | Timber.d("H264Encoder started successfully") |
| | | Timber.d("Output file: %s", outputFile.absolutePath) |
| | | return true |
| | | } else { |
| | | Log.e("MainActivity", "Failed to initialize H264Encoder") |
| | | Timber.e("Failed to initialize H264Encoder") |
| | | h264Encoder = null |
| | | return false |
| | | } |
| | | } catch (e: Exception) { |
| | | Log.e("MainActivity", "Failed to start H264Encoder", e) |
| | | Timber.e(e, "Failed to start H264Encoder") |
| | | h264Encoder = null |
| | | return false |
| | | } |
| | |
| | | h264Encoder?.let { encoder -> |
| | | try { |
| | | encoder.stop() |
| | | Log.d("MainActivity", "H264Encoder stopped") |
| | | Timber.d("H264Encoder stopped") |
| | | } catch (e: Exception) { |
| | | Log.e("MainActivity", "Failed to stop H264Encoder", e) |
| | | Timber.e(e, "Failed to stop H264Encoder") |
| | | } |
| | | h264Encoder = null |
| | | } |
| New file |
| | |
| | | package com.anyun.h264.model; |
| | | |
| | | /** |
| | | * 水印信息数据模型 |
| | | * 包含车牌、学员、教练、经度、纬度、驾校、车速等信息 |
| | | */ |
| | | public class WatermarkInfo { |
| | | /** 车牌号 */ |
| | | private String plateNumber; |
| | | |
| | | /** 学员姓名 */ |
| | | private String student; |
| | | |
| | | /** 教练姓名 */ |
| | | private String coach; |
| | | |
| | | /** 经度 */ |
| | | private Double longitude; |
| | | |
| | | /** 纬度 */ |
| | | private Double latitude; |
| | | |
| | | /** 驾校名称 */ |
| | | private String drivingSchool; |
| | | |
| | | /** 车速(单位:km/h) */ |
| | | private Double speed; |
| | | |
| | | public WatermarkInfo() { |
| | | } |
| | | |
| | | public String getPlateNumber() { |
| | | return plateNumber; |
| | | } |
| | | |
| | | public void setPlateNumber(String plateNumber) { |
| | | this.plateNumber = plateNumber; |
| | | } |
| | | |
| | | public String getStudent() { |
| | | return student; |
| | | } |
| | | |
| | | public void setStudent(String student) { |
| | | this.student = student; |
| | | } |
| | | |
| | | public String getCoach() { |
| | | return coach; |
| | | } |
| | | |
| | | public void setCoach(String coach) { |
| | | this.coach = coach; |
| | | } |
| | | |
| | | public Double getLongitude() { |
| | | return longitude; |
| | | } |
| | | |
| | | public void setLongitude(Double longitude) { |
| | | this.longitude = longitude; |
| | | } |
| | | |
| | | public Double getLatitude() { |
| | | return latitude; |
| | | } |
| | | |
| | | public void setLatitude(Double latitude) { |
| | | this.latitude = latitude; |
| | | } |
| | | |
| | | public String getDrivingSchool() { |
| | | return drivingSchool; |
| | | } |
| | | |
| | | public void setDrivingSchool(String drivingSchool) { |
| | | this.drivingSchool = drivingSchool; |
| | | } |
| | | |
| | | public Double getSpeed() { |
| | | return speed; |
| | | } |
| | | |
| | | public void setSpeed(Double speed) { |
| | | this.speed = speed; |
| | | } |
| | | |
| | | /** |
| | | * 格式化水印信息为显示文本 |
| | | * @return 格式化的水印文本 |
| | | */ |
| | | public String formatWatermarkText() { |
| | | StringBuilder sb = new StringBuilder(); |
| | | |
| | | if (plateNumber != null && !plateNumber.isEmpty()) { |
| | | sb.append("车牌:").append(plateNumber); |
| | | } |
| | | |
| | | if (student != null && !student.isEmpty()) { |
| | | if (sb.length() > 0) sb.append(" "); |
| | | sb.append("学员:").append(student); |
| | | } |
| | | |
| | | if (coach != null && !coach.isEmpty()) { |
| | | if (sb.length() > 0) sb.append(" "); |
| | | sb.append("教练:").append(coach); |
| | | } |
| | | |
| | | if (longitude != null && latitude != null) { |
| | | if (sb.length() > 0) sb.append(" "); |
| | | sb.append("位置:").append(String.format("%.6f,%.6f", latitude, longitude)); |
| | | } |
| | | |
| | | if (drivingSchool != null && !drivingSchool.isEmpty()) { |
| | | if (sb.length() > 0) sb.append(" "); |
| | | sb.append("驾校:").append(drivingSchool); |
| | | } |
| | | |
| | | if (speed != null) { |
| | | if (sb.length() > 0) sb.append(" "); |
| | | sb.append("车速:").append(String.format("%.1f", speed)).append("km/h"); |
| | | } |
| | | |
| | | return sb.toString(); |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "WatermarkInfo{" + |
| | | "plateNumber='" + plateNumber + '\'' + |
| | | ", student='" + student + '\'' + |
| | | ", coach='" + coach + '\'' + |
| | | ", longitude=" + longitude + |
| | | ", latitude=" + latitude + |
| | | ", drivingSchool='" + drivingSchool + '\'' + |
| | | ", speed=" + speed + |
| | | '}'; |
| | | } |
| | | } |
| | | |
| | |
| | | * // 获取资源列表 |
| | | * List<ResourceInfo> resources = client.getResourceList("240101000000", "240101235959"); |
| | | * |
| | | * // 设置水印信息 |
| | | * String watermarkJson = "{\"plateNumber\":\"京A12345\",\"student\":\"张三\",\"coach\":\"李四\",\"longitude\":116.397128,\"latitude\":39.916527,\"drivingSchool\":\"XX驾校\",\"speed\":60.5}"; |
| | | * client.setWatermarkInfo(watermarkJson); |
| | | * |
| | | * // 解绑服务 |
| | | * client.unbindService(); |
| | | * </pre> |
| | |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 设置水印信息 |
| | | * @param watermarkInfo JSON格式的水印信息字符串,包含:plateNumber(车牌)、student(学员)、coach(教练)、longitude(经度)、latitude(纬度)、drivingSchool(驾校)、speed(车速) |
| | | * 示例:{"plateNumber":"京A12345","student":"张三","coach":"李四","longitude":116.397128,"latitude":39.916527,"drivingSchool":"XX驾校","speed":60.5} |
| | | * 如果传入null或空字符串,将清除水印 |
| | | * @return true-成功,false-失败 |
| | | */ |
| | | public boolean setWatermarkInfo(String watermarkInfo) { |
| | | if (!isServiceBound()) { |
| | | Log.e(TAG, "Service is not bound"); |
| | | return false; |
| | | } |
| | | |
| | | try { |
| | | service.setWatermarkInfo(watermarkInfo); |
| | | Log.d(TAG, "setWatermarkInfo called with: " + watermarkInfo); |
| | | return true; |
| | | } catch (RemoteException e) { |
| | | Log.e(TAG, "Error calling setWatermarkInfo", e); |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | activityCompose = "1.8.0" |
| | | composeBom = "2024.04.01" |
| | | netty = "4.1.48.Final" |
| | | timber = "4.7.1" |
| | | |
| | | [libraries] |
| | | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } |
| | |
| | | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } |
| | | androidx-material3 = { group = "androidx.compose.material3", name = "material3" } |
| | | netty-all = { group = "io.netty", name = "netty-all", version.ref = "netty" } |
| | | timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" } |
| | | |
| | | [plugins] |
| | | android-application = { id = "com.android.application", version.ref = "agp" } |
| | |
| | | |
| | | # Provides a relative path to your source file(s). |
| | | src/main/cpp/libyuv.c |
| | | src/main/cpp/apptimer.cpp |
| | | src/main/cpp/charencode.cpp |
| | | src/main/cpp/watermark.cpp |
| | | src/main/cpp/ImageProc.c |
| | | src/main/cpp/charencode.c |
| | | src/main/cpp/jpeg.c |
| | | src/main/cpp/watermark.c |
| | | src/main/cpp/SonixCamera/ROMData.c |
| | | src/main/cpp/SonixCamera/SFData.c |
| | | src/main/cpp/SonixCamera/SonixCamera.c |
| | |
| | | jpeg |
| | | turbojpeg |
| | | ${log-lib} |
| | | ${g-lib}) |
| | | ${g-lib} |
| | | c++_shared |
| | | ) |
| | | |
| | | #target_link_libraries( # Specifies the target library. |
| | | # usbcamera-lib |
| | |
| | | |
| | | externalNativeBuild { |
| | | cmake { |
| | | arguments "-DANDROID_ARM_NEON=TRUE" |
| | | arguments "-DANDROID_ARM_NEON=TRUE","-DANDROID_STL=c++_shared" |
| | | cppFlags "" |
| | | } |
| | | } |
| | |
| | | pickFirst 'lib/armeabi-v7a/libturbojpeg.so' |
| | | pickFirst 'lib/arm64-v8a/libjpeg.so' |
| | | pickFirst 'lib/arm64-v8a/libturbojpeg.so' |
| | | pickFirst 'lib/*/libc++_shared.so' |
| | | |
| | | } |
| | | |
| | |
| | | (*env)->ReleaseStringUTFChars(env, name, except_camera_name); |
| | | } |
| | | |
| | | if (ret == SUCCESS_LOCAL) { |
| | | InitWatermark("/system/ms_unicode_24.bin", IMG_WIDTH, IMG_HEIGHT); |
| | | wm_enable = true; |
| | | } |
| | | |
| | | return ret; |
| | | } |
| | | |
| | |
| | | pthread_mutex_lock(&mutex); |
| | | cam_inited = false; |
| | | |
| | | wm_enable = false; |
| | | UninitWatermark(); |
| | | |
| | | LOGI("stopcapturing"); |
| | | stopcapturing(); |
| | | |
| New file |
| | |
| | | // |
| | | // Created by YY on 2021/3/16. |
| | | // |
| | | |
| | | #include "apptimer.h" |
| | | //#include "../jni_log.h" |
| | | |
| | | #include <cstdint> |
| | | #include <functional> |
| | | #include <chrono> |
| | | #include <mutex> |
| | | #include <condition_variable> |
| | | #include <list> |
| | | #include <thread> |
| | | #include <map> |
| | | #include <string> |
| | | #include <sstream> |
| | | #include <iomanip> |
| | | |
| | | #define DEBUG(fmt, args...) //LOGD("<apptimer> <%s>: " fmt, __func__, ##args) |
| | | |
| | | std::mutex mtx; |
| | | |
| | | std::map<void(*)(apptimer_var_t), CTimer *> TimerList; |
| | | |
| | | static void RemoveTimerList(const CTimer *p); |
| | | |
| | | CTimer::CTimer() { |
| | | flag = 0; |
| | | var.var_ptr = nullptr; |
| | | var.var1 = var.var2 = var.var_length = 0; |
| | | pthread = nullptr; |
| | | } |
| | | |
| | | CTimer::~CTimer() { |
| | | if (pthread != nullptr) { |
| | | delete pthread; |
| | | } |
| | | if (var.var_ptr != nullptr) { |
| | | delete var.var_ptr; |
| | | } |
| | | } |
| | | |
| | | bool CTimer::_updateStatus(CTimer *timer) { |
| | | if (timer->flag == 0) { |
| | | return false; |
| | | } else { |
| | | return true; //cancel |
| | | } |
| | | } |
| | | |
| | | void CTimer::_do_task_func(uint32_t msec, CTimer *timer, std::function<void(apptimer_var_t)> callback) { |
| | | std::unique_lock<std::mutex> lk(timer->cv_mtx); |
| | | |
| | | auto now = std::chrono::steady_clock::now(); |
| | | |
| | | if (timer->cv.wait_until(lk, now + std::chrono::milliseconds(msec), std::bind(_updateStatus, timer)) == false) { |
| | | RemoveTimerList(timer); // 从列表中删除索引 |
| | | |
| | | if (timer->flag == 0) { |
| | | callback(timer->var); |
| | | } |
| | | } else { |
| | | // LOGD("Cancel %d", static_cast<int>(timer->flag)); |
| | | RemoveTimerList(timer); |
| | | } |
| | | |
| | | //delete timer; |
| | | lk.unlock(); |
| | | // std::thread(clear, timer).detach(); |
| | | delete timer; |
| | | } |
| | | |
| | | void CTimer::copy(int value1, int value2, const void *data, int length) |
| | | { |
| | | this->var.var1 = value1; |
| | | this->var.var2 = value2; |
| | | this->var.var_length = length; |
| | | if (length > 0 && data != nullptr) { |
| | | this->var.var_ptr = new uint8_t[length]; |
| | | memcpy(this->var.var_ptr, data, length); |
| | | } else { |
| | | this->var.var_ptr = nullptr; |
| | | } |
| | | } |
| | | |
| | | void CTimer::clear(CTimer *timer) |
| | | { |
| | | if (timer != nullptr) { |
| | | delete timer; |
| | | } |
| | | } |
| | | |
| | | bool CTimer::start(uint32_t msec, std::function<void(apptimer_var_t)> callback) { |
| | | if (pthread == nullptr) { |
| | | try { |
| | | pthread = new std::thread(_do_task_func, msec, this, callback); |
| | | pthread->detach(); |
| | | } catch (std::exception &ex) { |
| | | DEBUG("%s", ex.what()); |
| | | return false; |
| | | } |
| | | // this->callbackThread = std::thread(_do_task_func, msec, this, callback); |
| | | // this->callbackThread.detach(); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | void CTimer::stop() { |
| | | std::unique_lock<std::mutex> lk(this->cv_mtx); |
| | | this->flag = 1; |
| | | this->cv.notify_one(); |
| | | } |
| | | |
| | | void AppTimer_init(void) |
| | | { |
| | | TimerList.clear(); |
| | | } |
| | | |
| | | void AppTimer_add(void(*cb)(apptimer_var_t), uint32_t msec) |
| | | { |
| | | CTimer *cTimer = new CTimer(); |
| | | cTimer->copy(0, 0, nullptr, 0); |
| | | |
| | | std::lock_guard<std::mutex> lock(mtx); |
| | | TimerList.insert(std::pair<void (*)(apptimer_var_t), CTimer *>(cb, cTimer)); |
| | | |
| | | if (cTimer->start(msec, cb) == false) { |
| | | auto it = TimerList.find(cb); |
| | | if (it != TimerList.end()) { |
| | | CTimer *ptr = it->second; |
| | | TimerList.erase(it); |
| | | } |
| | | delete cTimer; |
| | | } |
| | | } |
| | | |
| | | void AppTimer_add(void(*cb)(apptimer_var_t), uint32_t msec, int value1, int value2) |
| | | { |
| | | CTimer *cTimer = new CTimer(); |
| | | cTimer->copy(value1, value2, nullptr, 0); |
| | | |
| | | std::lock_guard<std::mutex> lock(mtx); |
| | | TimerList.insert(std::pair<void (*)(apptimer_var_t), CTimer *>(cb, cTimer)); |
| | | |
| | | if (cTimer->start(msec, cb) == false) { |
| | | auto it = TimerList.find(cb); |
| | | if (it != TimerList.end()) { |
| | | CTimer *ptr = it->second; |
| | | TimerList.erase(it); |
| | | } |
| | | delete cTimer; |
| | | } |
| | | } |
| | | |
| | | void AppTimer_add(void(*cb)(apptimer_var_t), uint32_t msec, void *data, int lenght) |
| | | { |
| | | CTimer *cTimer = new CTimer(); |
| | | cTimer->copy(0, 0, data, lenght); |
| | | |
| | | std::lock_guard<std::mutex> lock(mtx); |
| | | TimerList.insert(std::pair<void (*)(apptimer_var_t), CTimer *>(cb, cTimer)); |
| | | |
| | | if (cTimer->start(msec, cb) == false) { |
| | | auto it = TimerList.find(cb); |
| | | if (it != TimerList.end()) { |
| | | CTimer *ptr = it->second; |
| | | TimerList.erase(it); |
| | | } |
| | | delete cTimer; |
| | | } |
| | | } |
| | | |
| | | void AppTimer_delete(void(*cb)(apptimer_var_t)) |
| | | { |
| | | std::lock_guard<std::mutex> lock(mtx); |
| | | |
| | | auto it = TimerList.find(cb); |
| | | if (it != TimerList.end()) { |
| | | CTimer *ptr = it->second; |
| | | TimerList.erase(it); |
| | | ptr->stop(); |
| | | } |
| | | } |
| | | |
| | | static void RemoveTimerList(const CTimer *p) |
| | | { |
| | | std::lock_guard<std::mutex> lock(mtx); |
| | | |
| | | for (auto it = TimerList.begin(); it != TimerList.end(); ++it) { |
| | | CTimer *ptr = it->second; |
| | | if (ptr == p) { |
| | | TimerList.erase(it); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | uint32_t AppTimer_GetTickCount(void) |
| | | { |
| | | std::chrono::milliseconds as = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()); |
| | | |
| | | return as.count(); |
| | | } |
| | | |
| | | uint64_t AppTimer_GetGmtTickCount(void) |
| | | { |
| | | std::chrono::milliseconds as = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()); |
| | | |
| | | return as.count(); |
| | | } |
| | | |
| | | std::string FormatTime(const char *fmt) |
| | | { |
| | | auto now = std::chrono::system_clock::now(); |
| | | auto timet = std::chrono::system_clock::to_time_t(now); |
| | | auto localTime = *std::localtime(&timet); |
| | | |
| | | std::stringstream ss; |
| | | std::string str; |
| | | ss << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S"); |
| | | str = ss.str(); |
| | | return str; |
| | | } |
| | | |
| | | std::time_t MktimeString(const char *str, const char *fmt) |
| | | { |
| | | struct std::tm dt; |
| | | std::istringstream ss(str); |
| | | |
| | | ss >> std::get_time(&dt, fmt); |
| | | |
| | | std::stringstream out; |
| | | out << std::put_time(&dt, "%Y-%m-%d %H:%M:%S"); |
| | | // DEBUG("%s, %ld", out.str().c_str(), std::mktime(&dt)); |
| | | |
| | | time_t utc = std::mktime(&dt); |
| | | |
| | | return utc; |
| | | } |
| | | |
| | | time_t TimeStamp(int year, int mon, int day, int hour, int min, int sec) |
| | | { |
| | | // YYHHDDhhmmss(GMT+8) |
| | | struct tm test_tm; |
| | | |
| | | struct timeval tv; |
| | | struct timezone tz; |
| | | |
| | | gettimeofday(&tv,&tz); |
| | | |
| | | memset(&test_tm, 0, sizeof(test_tm)); |
| | | |
| | | test_tm.tm_year = year - 1900; |
| | | test_tm.tm_mon = mon - 1; |
| | | test_tm.tm_mday = day; |
| | | test_tm.tm_hour = hour; |
| | | test_tm.tm_min = min; |
| | | test_tm.tm_sec = sec; |
| | | |
| | | return mktime(&test_tm) - tz.tz_minuteswest*60; |
| | | } |
| New file |
| | |
| | | // |
| | | // Created by YY on 2021/3/16. |
| | | // |
| | | |
| | | #ifndef NDKADV_APPTIMER_H |
| | | #define NDKADV_APPTIMER_H |
| | | |
| | | #include <cstdint> |
| | | #include <functional> |
| | | #include <thread> |
| | | #include <chrono> |
| | | #include <atomic> |
| | | #include <mutex> |
| | | #include <condition_variable> |
| | | #include <string> |
| | | #include <ctime> |
| | | |
| | | #define D_SEC(n) ((n)*1000UL) |
| | | #define D_MIN(n) ((n)*1000UL*60UL) |
| | | #define D_HOUR(n) ((n)*1000UL*60UL*60UL) |
| | | |
| | | typedef struct { |
| | | int var1; |
| | | int var2; |
| | | void * var_ptr; |
| | | int var_length; |
| | | } apptimer_var_t; |
| | | |
| | | class CTimer { |
| | | public: |
| | | CTimer(); |
| | | virtual ~CTimer(); |
| | | static bool _updateStatus(CTimer *timer); |
| | | static void _do_task_func(uint32_t msec, CTimer *timer, std::function<void(apptimer_var_t)> callback); |
| | | bool start(uint32_t msec, std::function<void(apptimer_var_t)> callback); |
| | | void stop(); |
| | | void copy(int value1, int value2, const void *data, int length); |
| | | static void clear(CTimer *timer); |
| | | private: |
| | | std::mutex cv_mtx; |
| | | std::condition_variable cv; |
| | | // std::thread callbackThread; |
| | | std::thread *pthread; |
| | | apptimer_var_t var; |
| | | public: |
| | | std::atomic_int flag; |
| | | }; |
| | | |
| | | void AppTimer_init(void); |
| | | |
| | | void AppTimer_add(void(*cb)(apptimer_var_t), uint32_t msec); |
| | | |
| | | void AppTimer_add(void(*cb)(apptimer_var_t), uint32_t msec, int value1, int value2); |
| | | |
| | | void AppTimer_add(void(*cb)(apptimer_var_t), uint32_t msec, void *data, int lenght); |
| | | |
| | | void AppTimer_delete(void (*cb) (apptimer_var_t)); |
| | | |
| | | uint32_t AppTimer_GetTickCount(void); |
| | | |
| | | uint64_t AppTimer_GetGmtTickCount(void); |
| | | |
| | | std::string FormatTime(const char *fmt = "%Y-%m-%d %H:%M:%S"); |
| | | |
| | | std::time_t MktimeString(const char *str, const char *fmt); |
| | | |
| | | time_t TimeStamp(int year, int mon, int day, int hour, int min, int sec); |
| | | |
| | | #endif //NDKADV_APPTIMER_H |
| New file |
| | |
| | | // |
| | | // Created by YY on 2021/10/22. |
| | | // |
| | | |
| | | #include <stdlib.h> |
| | | #include "charencode.h" |
| | | |
| | | /***************************************************************************** |
| | | * 将一个字符的Unicode(UCS-2和UCS-4)编码转换成UTF-8编码. |
| | | * |
| | | * 参数: |
| | | * unic 字符的Unicode编码值 |
| | | * pOutput 指向输出的用于存储UTF8编码值的缓冲区的指针 |
| | | * outsize pOutput缓冲的大小 |
| | | * |
| | | * 返回值: |
| | | * 返回转换后的字符的UTF8编码所占的字节数, 如果出错则返回 0 . |
| | | * |
| | | * 注意: |
| | | * 1. UTF8没有字节序问题, 但是Unicode有字节序要求; |
| | | * 字节序分为大端(Big Endian)和小端(Little Endian)两种; |
| | | * 在Intel处理器中采用小端法表示, 在此采用小端法表示. (低地址存低位) |
| | | * 2. 请保证 pOutput 缓冲区有最少有 6 字节的空间大小! |
| | | ****************************************************************************/ |
| | | int enc_unicode_to_utf8_one(unsigned long unic, unsigned char *pOutput, |
| | | int outSize) |
| | | { |
| | | if (pOutput == NULL || outSize < 6) |
| | | return 0; |
| | | |
| | | if (unic <= 0x0000007F) |
| | | { |
| | | // * U-00000000 - U-0000007F: 0xxxxxxx |
| | | *pOutput = (unic & 0x7F); |
| | | return 1; |
| | | } |
| | | else if (unic >= 0x00000080 && unic <= 0x000007FF) |
| | | { |
| | | // * U-00000080 - U-000007FF: 110xxxxx 10xxxxxx |
| | | *(pOutput + 1) = (unic & 0x3F) | 0x80; |
| | | *pOutput = ((unic >> 6) & 0x1F) | 0xC0; |
| | | return 2; |
| | | } |
| | | else if (unic >= 0x00000800 && unic <= 0x0000FFFF) |
| | | { |
| | | // * U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx |
| | | *(pOutput + 2) = (unic & 0x3F) | 0x80; |
| | | *(pOutput + 1) = ((unic >> 6) & 0x3F) | 0x80; |
| | | *pOutput = ((unic >> 12) & 0x0F) | 0xE0; |
| | | return 3; |
| | | } |
| | | else if (unic >= 0x00010000 && unic <= 0x001FFFFF) |
| | | { |
| | | // * U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
| | | *(pOutput + 3) = (unic & 0x3F) | 0x80; |
| | | *(pOutput + 2) = ((unic >> 6) & 0x3F) | 0x80; |
| | | *(pOutput + 1) = ((unic >> 12) & 0x3F) | 0x80; |
| | | *pOutput = ((unic >> 18) & 0x07) | 0xF0; |
| | | return 4; |
| | | } |
| | | else if (unic >= 0x00200000 && unic <= 0x03FFFFFF) |
| | | { |
| | | // * U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
| | | *(pOutput + 4) = (unic & 0x3F) | 0x80; |
| | | *(pOutput + 3) = ((unic >> 6) & 0x3F) | 0x80; |
| | | *(pOutput + 2) = ((unic >> 12) & 0x3F) | 0x80; |
| | | *(pOutput + 1) = ((unic >> 18) & 0x3F) | 0x80; |
| | | *pOutput = ((unic >> 24) & 0x03) | 0xF8; |
| | | return 5; |
| | | } |
| | | else if (unic >= 0x04000000 && unic <= 0x7FFFFFFF) |
| | | { |
| | | // * U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
| | | *(pOutput + 5) = (unic & 0x3F) | 0x80; |
| | | *(pOutput + 4) = ((unic >> 6) & 0x3F) | 0x80; |
| | | *(pOutput + 3) = ((unic >> 12) & 0x3F) | 0x80; |
| | | *(pOutput + 2) = ((unic >> 18) & 0x3F) | 0x80; |
| | | *(pOutput + 1) = ((unic >> 24) & 0x3F) | 0x80; |
| | | *pOutput = ((unic >> 30) & 0x01) | 0xFC; |
| | | return 6; |
| | | } |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | /***************************************************************************** |
| | | * 将一个字符的UTF8编码转换成Unicode(UCS-2和UCS-4)编码. |
| | | * |
| | | * 参数: |
| | | * pInput 指向输入缓冲区, 以UTF-8编码 |
| | | * Unic 指向输出缓冲区, 其保存的数据即是Unicode编码值, |
| | | * 类型为unsigned long . |
| | | * |
| | | * 返回值: |
| | | * 成功则返回该字符的UTF8编码所占用的字节数; 失败则返回0. |
| | | * |
| | | * 注意: |
| | | * 1. UTF8没有字节序问题, 但是Unicode有字节序要求; |
| | | * 字节序分为大端(Big Endian)和小端(Little Endian)两种; |
| | | * 在Intel处理器中采用小端法表示, 在此采用小端法表示. (低地址存低位) |
| | | ****************************************************************************/ |
| | | int enc_utf8_to_unicode_one(const unsigned char* pInput, unsigned long *Unic) |
| | | { |
| | | if (pInput == NULL || Unic == NULL) |
| | | return 0; |
| | | |
| | | // b1 表示UTF-8编码的pInput中的高字节, b2 表示次高字节, ... |
| | | char b1, b2, b3, b4, b5, b6; |
| | | |
| | | *Unic = 0x0; // 把 *Unic 初始化为全零 |
| | | int utfbytes = enc_get_utf8_size(*pInput); |
| | | unsigned char *pOutput = (unsigned char *)Unic; |
| | | |
| | | switch (utfbytes) |
| | | { |
| | | case 0: |
| | | *pOutput = *pInput; |
| | | utfbytes += 1; |
| | | break; |
| | | case 2: |
| | | b1 = *pInput; |
| | | b2 = *(pInput + 1); |
| | | if ((b2 & 0xE0) != 0x80) |
| | | return 0; |
| | | *pOutput = (b1 << 6) + (b2 & 0x3F); |
| | | *(pOutput + 1) = (b1 >> 2) & 0x07; |
| | | break; |
| | | case 3: |
| | | b1 = *pInput; |
| | | b2 = *(pInput + 1); |
| | | b3 = *(pInput + 2); |
| | | if (((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80)) |
| | | return 0; |
| | | *pOutput = (b2 << 6) + (b3 & 0x3F); |
| | | *(pOutput + 1) = (b1 << 4) + ((b2 >> 2) & 0x0F); |
| | | break; |
| | | case 4: |
| | | b1 = *pInput; |
| | | b2 = *(pInput + 1); |
| | | b3 = *(pInput + 2); |
| | | b4 = *(pInput + 3); |
| | | if (((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) |
| | | || ((b4 & 0xC0) != 0x80)) |
| | | return 0; |
| | | *pOutput = (b3 << 6) + (b4 & 0x3F); |
| | | *(pOutput + 1) = (b2 << 4) + ((b3 >> 2) & 0x0F); |
| | | *(pOutput + 2) = ((b1 << 2) & 0x1C) + ((b2 >> 4) & 0x03); |
| | | break; |
| | | case 5: |
| | | b1 = *pInput; |
| | | b2 = *(pInput + 1); |
| | | b3 = *(pInput + 2); |
| | | b4 = *(pInput + 3); |
| | | b5 = *(pInput + 4); |
| | | if (((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) |
| | | || ((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80)) |
| | | return 0; |
| | | *pOutput = (b4 << 6) + (b5 & 0x3F); |
| | | *(pOutput + 1) = (b3 << 4) + ((b4 >> 2) & 0x0F); |
| | | *(pOutput + 2) = (b2 << 2) + ((b3 >> 4) & 0x03); |
| | | *(pOutput + 3) = (b1 << 6); |
| | | break; |
| | | case 6: |
| | | b1 = *pInput; |
| | | b2 = *(pInput + 1); |
| | | b3 = *(pInput + 2); |
| | | b4 = *(pInput + 3); |
| | | b5 = *(pInput + 4); |
| | | b6 = *(pInput + 5); |
| | | if (((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) |
| | | || ((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80) |
| | | || ((b6 & 0xC0) != 0x80)) |
| | | return 0; |
| | | *pOutput = (b5 << 6) + (b6 & 0x3F); |
| | | *(pOutput + 1) = (b5 << 4) + ((b6 >> 2) & 0x0F); |
| | | *(pOutput + 2) = (b3 << 2) + ((b4 >> 4) & 0x03); |
| | | *(pOutput + 3) = ((b1 << 6) & 0x40) + (b2 & 0x3F); |
| | | break; |
| | | default: |
| | | return 0; |
| | | break; |
| | | } |
| | | |
| | | return utfbytes; |
| | | } |
| | | |
| | | int enc_get_utf8_size(const unsigned char pInput) |
| | | { |
| | | unsigned char c = pInput; |
| | | // 0xxxxxxx 返回0 |
| | | // 10xxxxxx 不存在 |
| | | // 110xxxxx 返回2 |
| | | // 1110xxxx 返回3 |
| | | // 11110xxx 返回4 |
| | | // 111110xx 返回5 |
| | | // 1111110x 返回6 |
| | | if(c< 0x80) return 0; |
| | | if(c>=0x80 && c<0xC0) return -1; |
| | | if(c>=0xC0 && c<0xE0) return 2; |
| | | if(c>=0xE0 && c<0xF0) return 3; |
| | | if(c>=0xF0 && c<0xF8) return 4; |
| | | if(c>=0xF8 && c<0xFC) return 5; |
| | | if(c>=0xFC) return 6; |
| | | } |
| File was renamed from usbcameralib/src/main/cpp/watermark.c |
| | |
| | | #include "watermark.h" |
| | | #include "charencode.h" |
| | | #include "ImageProc.h" |
| | | #include "apptimer.h" |
| | | |
| | | //static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; |
| | | typedef enum { |
| | |
| | | static int pic_width = 0, pic_height = 0; |
| | | static color_t wm_color; |
| | | |
| | | static void PrintTime(apptimer_var_t val) |
| | | { |
| | | text_t text; |
| | | |
| | | text.x = 10; |
| | | text.y = 10; |
| | | |
| | | memset(text.text, 0, sizeof (text.text)); |
| | | |
| | | strcpy(text.text, FormatTime().c_str()); |
| | | |
| | | PrepareWatermark(RED, 24, 2, 1, &text); |
| | | |
| | | AppTimer_add(PrintTime, D_SEC(1)); |
| | | } |
| | | |
| | | void InitWatermark(const char *font, int width, int height) |
| | | { |
| | | LOGI("InitWatermark"); |
| | |
| | | |
| | | pic_width = width; |
| | | pic_height = height; |
| | | |
| | | AppTimer_add(PrintTime, D_SEC(1)); |
| | | } |
| | | |
| | | void UninitWatermark(void) |
| | |
| | | wm = NULL; |
| | | } |
| | | wm_num = 0; |
| | | AppTimer_delete(PrintTime); |
| | | } |
| | | |
| | | void PrepareWatermark(int color, int font_size, int multiple, int num, const text_t *texts) |
| | |
| | | const int bytes_per_line = font_size / 8; |
| | | |
| | | wm_num = 0; |
| | | wm_color = color; |
| | | wm_color = static_cast<color_t>(color); |
| | | |
| | | //FILE *fp = fopen("/storage/self/primary/ms_unicode.bin", "rb"); |
| | | //FILE *fp = fopen("/system/ms_unicode.bin", "rb"); |
| | |
| | | uint8_t zm[64*64/8]; // 最大 |
| | | |
| | | for (int m = 0; m < num; ++m) { |
| | | char *str = texts[m].text; |
| | | const char *str = texts[m].text; |
| | | |
| | | int screen_x = texts[m].x; |
| | | int screen_y = texts[m].y; |
| | | offset = 0; |
| | | |
| | | while (str != NULL && offset < strlen(str)) { |
| | | int skip = enc_utf8_to_unicode_one(str + offset, &unicode); |
| | | int skip = enc_utf8_to_unicode_one(reinterpret_cast<const unsigned char *>(str + offset), &unicode); |
| | | int next_x = 0; |
| | | |
| | | if (skip == 0) { |
| | |
| | | #ifndef FLOATWINDOWVEDIO_WATERMARK_H |
| | | #define FLOATWINDOWVEDIO_WATERMARK_H |
| | | |
| | | #ifdef __cplusplus |
| | | extern "C" { |
| | | #endif |
| | | |
| | | typedef struct { |
| | | int x; |
| | | int y; |
| | |
| | | |
| | | void AddWatermark(uint8_t *mem); |
| | | |
| | | #ifdef __cplusplus |
| | | } |
| | | #endif |
| | | |
| | | #endif //FLOATWINDOWVEDIO_WATERMARK_H |