| | |
| | | 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; |
| | | |
| | |
| | | @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; |
| | | } |
| | | |
| | |
| | | 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)) { |
| | | 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[] resolution = {width, height}; |
| | | if (h264Encoder.initialize(DEFAULT_CAMERA_ID_RANGE, null, resolution, false)) { |
| | | 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.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"); |
| | | } |
| | | |
| | | @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; |
| | | } |
| | | } |
| | |
| | | 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; |
| | | } |
| | | } |