| New file |
| | |
| | | # H264æä»¶æ£æ¥å·¥å
·ä½¿ç¨è¯´æ |
| | | |
| | | ## å¿«éæ£æ¥ |
| | | |
| | | ### æ¹æ³1: ç´æ¥ä»è®¾å¤è·¯å¾æ£æ¥ï¼å¦ææä»¶å¨æ¬å°ï¼ |
| | | |
| | | ```bash |
| | | python check_h264.py test.h264 |
| | | ``` |
| | | |
| | | ### æ¹æ³2: ä»Android设å¤ä¸è½½æä»¶åæ£æ¥ |
| | | |
| | | 1. **è¿æ¥Android设å¤å¹¶å¯ç¨USBè°è¯** |
| | | |
| | | 2. **æ¥æ¾æä»¶è·¯å¾** |
| | | |
| | | Android设å¤ä¸çæä»¶è·¯å¾é常æ¯ï¼ |
| | | ``` |
| | | /storage/emulated/0/Android/data/com.anyun.h264/files/test.h264 |
| | | ``` |
| | | |
| | | æè
使ç¨adbæ¥æ¾ï¼ |
| | | ```bash |
| | | adb shell "find /sdcard -name test.h264 2>/dev/null" |
| | | ``` |
| | | |
| | | 3. **ä¸è½½æä»¶å°æ¬å°** |
| | | ```bash |
| | | adb pull /storage/emulated/0/Android/data/com.anyun.h264/files/test.h264 ./test.h264 |
| | | ``` |
| | | |
| | | 4. **è¿è¡æ£æ¥å·¥å
·** |
| | | ```bash |
| | | python check_h264.py test.h264 |
| | | ``` |
| | | |
| | | ## æ£æ¥å
容 |
| | | |
| | | å·¥å
·ä¼æ£æ¥ä»¥ä¸å
å®¹ï¼ |
| | | |
| | | 1. â
**æä»¶æ¯å¦åå¨** |
| | | 2. â
**æä»¶å¤§å°**ï¼åºè¯¥å¤§äº100åèï¼ |
| | | 3. â
**NALUåå
æ°é**ï¼è³å°åºè¯¥æSPS/PPS + 1个å
³é®å¸§ï¼ |
| | | 4. â
**æ¯å¦å
å«SPS**ï¼åºååæ°éï¼å¿
éï¼ |
| | | 5. â
**æ¯å¦å
å«PPS**ï¼å¾ååæ°éï¼å¿
éï¼ |
| | | 6. â
**æ¯å¦å
å«IDRå
³é®å¸§**ï¼å¿
éï¼ |
| | | 7. â
**æ¯å¦æå¤ä¸ªå¸§** |
| | | |
| | | ## é¢æç»æ |
| | | |
| | | ä¸ä¸ªå¯ä»¥ææ¾çH264æä»¶åºè¯¥å
å«ï¼ |
| | | |
| | | - â
è³å°1个SPS (ç±»å7) |
| | | - â
è³å°1个PPS (ç±»å8) |
| | | - â
è³å°1个IDRå
³é®å¸§ (ç±»å5) |
| | | - â
å¤ä¸ªNALUåå
ï¼å»ºè®®>10ä¸ªï¼ |
| | | |
| | | ## 常è§é®é¢ |
| | | |
| | | ### Q: æä»¶åªæ1帧æä¹åï¼ |
| | | |
| | | **A:** è¿æ¯ä¹åçé®é¢ãç°å¨ç代ç å·²ç»ä¿®å¤ï¼ |
| | | - â
æ£ç¡®å¤çSPS/PPSé
ç½® |
| | | - â
å¨å
³é®å¸§æ¶åå¹¶SPS/PPS |
| | | - â
æ¹è¿ç¼ç 循ç¯ï¼ç¡®ä¿å¤çææè¾åº |
| | | - â
æ·»å æ¸
空ç¼ç å¨åè½ |
| | | |
| | | **建议ï¼** |
| | | 1. éæ°ç¼è¯å¹¶è¿è¡åºç¨ |
| | | 2. å½å¶è³å°3-5ç§è§é¢ |
| | | 3. 忢ç¼ç åæ£æ¥æä»¶ |
| | | |
| | | ### Q: æä»¶æ æ³ææ¾æä¹åï¼ |
| | | |
| | | **å¯è½åå ï¼** |
| | | 1. â 缺å°SPS/PPS - æ£æ¥æ¯å¦è¾åºäºé
ç½®æ°æ® |
| | | 2. â æä»¶æ ¼å¼é误 - æ£æ¥æ¯å¦æ¯Annex-Bæ ¼å¼ |
| | | 3. â åªæ1帧 - æ£æ¥ç¼ç å¾ªç¯æ¯å¦æ£å¸¸å·¥ä½ |
| | | |
| | | **è§£å³æ¹æ¡ï¼** |
| | | 1. æ¥çLogcatæ¥å¿ï¼æç´¢ "H264Encoder" |
| | | 2. æ£æ¥æ¯å¦æ "SPS/PPS included" æ¥å¿ |
| | | 3. æ£æ¥æ¯å¦æ "Frame encoded" æ¥å¿ |
| | | 4. ä½¿ç¨æ¤å·¥å
·æ£æ¥æä»¶ç»æ |
| | | |
| | | ### Q: å¦ä½ææ¾H264æä»¶ï¼ |
| | | |
| | | å¯ä»¥ä½¿ç¨ä»¥ä¸ææ¾å¨ï¼ |
| | | - **VLC Media Player**ï¼æ¨èï¼ |
| | | - **ffplay** (FFmpegèªå¸¦) |
| | | - **MPC-HC** |
| | | - **PotPlayer** |
| | | |
| | | ç´æ¥åå» `.h264` æä»¶æææ¾å°ææ¾å¨çªå£å³å¯ã |
| | | |
| | | ## è°è¯å»ºè®® |
| | | |
| | | 妿æä»¶æ æ³ææ¾ï¼è¯·æ£æ¥ï¼ |
| | | |
| | | 1. **æ¥çLogcatæ¥å¿** |
| | | ```bash |
| | | adb logcat -s H264Encoder:D MainActivity:D |
| | | ``` |
| | | |
| | | 2. **æ£æ¥å
³é®æ¥å¿** |
| | | - "SPS/PPS included in key frame data" - 说æSPS/PPSå·²åå¹¶ |
| | | - "Frame encoded: ..." - 说ææå¸§è¾åº |
| | | - "Encoder output EOS" - è¯´ææ£å¸¸ç»æ |
| | | |
| | | 3. **éªè¯æä»¶ç»æ** |
| | | ```bash |
| | | python check_h264.py test.h264 |
| | | ``` |
| | | |
| | | 4. **使ç¨hexdumpæ¥çæä»¶å¤´** |
| | | ```bash |
| | | hexdump -C test.h264 | head -20 |
| | | ``` |
| | | åºè¯¥çå°ï¼`00 00 00 01` æ `00 00 01`ï¼Annex-Bèµ·å§ç ï¼ |
| | | |
| | |
| | | |
| | | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
| | | } |
| | | buildFeatures{ |
| | | aidl true |
| | | } |
| | | signingConfigs{ |
| | | |
| | | |
| | |
| | | implementation libs.androidx.ui.graphics |
| | | implementation libs.androidx.ui.tooling.preview |
| | | implementation libs.androidx.material3 |
| | | implementation libs.netty.all |
| | | testImplementation libs.junit |
| | | androidTestImplementation libs.androidx.junit |
| | | androidTestImplementation libs.androidx.espresso.core |
| | |
| | | <category android:name="android.intent.category.LAUNCHER" /> |
| | | </intent-filter> |
| | | </activity> |
| | | |
| | | <!-- H264ç¼ç æå¡ --> |
| | | <service |
| | | android:name=".H264EncodeService" |
| | | android:enabled="true" |
| | | android:exported="true"> |
| | | <intent-filter> |
| | | <action android:name="com.anyun.h264.H264EncodeService" /> |
| | | </intent-filter> |
| | | </service> |
| | | </application> |
| | | |
| | | </manifest> |
| New file |
| | |
| | | package com.anyun.h264; |
| | | |
| | | import com.anyun.h264.model.ResourceInfo; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * H264ç¼ç æå¡AIDLæ¥å£ |
| | | */ |
| | | interface IH264EncodeService { |
| | | /** |
| | | * æ§å¶H264ç¼ç åæä»¶ä¼ è¾ |
| | | * @param action æä½ç±»åï¼ |
| | | * 0-å¼å¯h264æä»¶åå
¥ï¼ |
| | | * 1-忢h264ç¼ç 并忢åå
¥æä»¶ï¼ |
| | | * 2-å¼å¯ç½ç»æ¨éh264ï¼ä¸åå
¥æä»¶ï¼ï¼ |
| | | * 3-忢h264ç¼ç 并忢ç½ç»æ¨éï¼ |
| | | * 4-å¼å§ä¼ è¾H264æä»¶ï¼ä»æä»¶è¯»åå¹¶ç½ç»æ¨éï¼ï¼ |
| | | * 5-忢H264æä»¶ä¼ è¾ |
| | | * @param jsonConfig JSONæ ¼å¼çé
ç½®åæ° |
| | | * action 0/2: å
å«ï¼ipï¼æå¡å¨IPï¼ãportï¼æå¡å¨ç«¯å£ï¼ãwidthï¼è§é¢å®½åº¦ï¼ãheightï¼è§é¢é«åº¦ï¼ãframerateï¼å¸§çï¼ãsimPhoneï¼SIMå¡å·ï¼ |
| | | * 示ä¾ï¼{"ip":"192.168.1.100","port":8888,"width":640,"height":480,"framerate":25,"simPhone":"013120122580"} |
| | | * action 4: å
å«ï¼ipï¼æå¡å¨IPï¼ãportï¼æå¡å¨ç«¯å£ï¼ãframerateï¼å¸§çï¼ãsimPhoneï¼SIMå¡å·ï¼ãfilePathï¼H264æä»¶è·¯å¾ï¼ãprotocolTypeï¼å议类åï¼å¯éï¼1-UDPï¼2-TCPï¼é»è®¤TCPï¼ |
| | | * 示ä¾ï¼{"ip":"192.168.1.100","port":8888,"framerate":25,"simPhone":"013120122580","filePath":"/sdcard/video.h264","protocolType":2} |
| | | * action 1/3/5: æ¤åæ°å¯ä¸ºç©ºænullï¼åæ¢æä½ä¸éè¦é
ç½®ï¼ |
| | | * @return 0-æåï¼1-失败 |
| | | */ |
| | | int controlEncode(int action, String jsonConfig); |
| | | |
| | | /** |
| | | * è·åèµæºå表 |
| | | * @param startTime å¼å§æ¶é´ï¼æ ¼å¼ï¼YYMMDDHHmmssï¼BCDç¼ç ç6åèåç¬¦ä¸²ï¼ |
| | | * @param endTime ç»ææ¶é´ï¼æ ¼å¼ï¼YYMMDDHHmmssï¼BCDç¼ç ç6åèåç¬¦ä¸²ï¼ |
| | | * @return èµæºåè¡¨ï¼æ ¹æ®JT/T 1076-2016表23å®ä¹ï¼ |
| | | */ |
| | | List<ResourceInfo> getResourceList(String startTime, String endTime); |
| | | } |
| | | |
| New file |
| | |
| | | package com.anyun.h264.model; |
| | | |
| | | /** |
| | | * é³è§é¢èµæºä¿¡æ¯ï¼æ ¹æ®JT/T 1076-2016表23å®ä¹ï¼ |
| | | */ |
| | | parcelable ResourceInfo; |
| | | |
| | |
| | | // 2. åå§åAACç¼ç å¨ |
| | | initEncoder(); |
| | | |
| | | // 3. åå§åUDP Socket |
| | | if (!protocolHelper.initializeUdpSocket()) { |
| | | // 3. åå§åSocketï¼UDPæTCPï¼æ ¹æ®å议类åèªå¨éæ©ï¼ |
| | | if (!protocolHelper.initializeSocket()) { |
| | | return false; |
| | | } |
| | | |
| | |
| | | byte[] rtpPacket = protocolHelper.createAudioRtpPacket( |
| | | packetData, timestamp, JT1076ProtocolHelper.DATA_TYPE_AUDIO, packetMark); |
| | | |
| | | // åéUDPå
|
| | | protocolHelper.sendUdpPacket(rtpPacket); |
| | | // åéRTPå
ï¼UDPæTCPï¼æ ¹æ®å议类åèªå¨éæ©ï¼ |
| | | protocolHelper.sendPacket(rtpPacket); |
| | | |
| | | offset += packetDataSize; |
| | | } |
| | |
| | | audioRecord = null; |
| | | } |
| | | |
| | | // å
³éUDP Socket |
| | | // å
³éSocketï¼UDPæTCPï¼æ ¹æ®å议类åèªå¨éæ©ï¼ |
| | | if (protocolHelper != null) { |
| | | protocolHelper.closeUdpSocket(); |
| | | protocolHelper.closeSocket(); |
| | | } |
| | | |
| | | Log.d(TAG, "AAC encoder stopped"); |
| New file |
| | |
| | | package com.anyun.h264; |
| | | |
| | | import android.app.Service; |
| | | import android.content.Intent; |
| | | import android.os.IBinder; |
| | | import android.os.RemoteException; |
| | | import android.util.Log; |
| | | |
| | | import com.anyun.h264.model.ResourceInfo; |
| | | |
| | | import org.json.JSONException; |
| | | import org.json.JSONObject; |
| | | |
| | | import java.io.File; |
| | | import java.text.ParseException; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | |
| | | /** |
| | | * H264ç¼ç æå¡ |
| | | * æä¾AIDLæ¥å£ä¾å®¢æ·ç«¯è°ç¨ï¼ç¨äºæ§å¶H264ç¼ç åæ¥è¯¢èµæºå表 |
| | | */ |
| | | public class H264EncodeService extends Service { |
| | | private static final String TAG = "H264EncodeService"; |
| | | |
| | | private H264Encoder h264Encoder; |
| | | private H264FileTransmitter h264FileTransmitter; // H264æä»¶ä¼ è¾å¨ |
| | | private String outputFileDirectory; // H264æä»¶è¾åºç®å½ |
| | | |
| | | // é»è®¤ç¼ç åæ° |
| | | private static final int DEFAULT_WIDTH = 640; |
| | | private static final int DEFAULT_HEIGHT = 480; |
| | | private static final int DEFAULT_FRAME_RATE = 25; |
| | | private static final int DEFAULT_BITRATE = 2000000; // 2Mbps |
| | | |
| | | // é»è®¤æå头忰 |
| | | private static final int[] DEFAULT_CAMERA_ID_RANGE = {1, 2}; |
| | | private static final int[] DEFAULT_RESOLUTION = {640, 480}; |
| | | |
| | | // AIDLæ¥å£å®ç° |
| | | private final IH264EncodeService.Stub binder = new IH264EncodeService.Stub() { |
| | | @Override |
| | | public int controlEncode(int action, String jsonConfig) throws RemoteException { |
| | | return H264EncodeService.this.controlEncode(action, jsonConfig); |
| | | } |
| | | |
| | | @Override |
| | | public List<ResourceInfo> getResourceList(String startTime, String endTime) throws RemoteException { |
| | | return H264EncodeService.this.getResourceList(startTime, endTime); |
| | | } |
| | | }; |
| | | |
| | | @Override |
| | | public void onCreate() { |
| | | super.onCreate(); |
| | | Log.d(TAG, "H264EncodeService created"); |
| | | |
| | | // åå§åè¾åºæä»¶ç®å½ï¼ä½¿ç¨åºç¨å¤é¨åå¨ç®å½ï¼ |
| | | outputFileDirectory = getExternalFilesDir(null).getAbsolutePath(); |
| | | Log.d(TAG, "Output file directory: " + outputFileDirectory); |
| | | } |
| | | |
| | | @Override |
| | | public IBinder onBind(Intent intent) { |
| | | Log.d(TAG, "Service bound"); |
| | | return binder; |
| | | } |
| | | |
| | | @Override |
| | | public boolean onUnbind(Intent intent) { |
| | | Log.d(TAG, "Service unbound"); |
| | | // ä¸èªå¨åæ¢ç¼ç å¨ï¼è®©å®å¨æå¡ä¸ä¿æè¿è¡ |
| | | return super.onUnbind(intent); |
| | | } |
| | | |
| | | @Override |
| | | public void onDestroy() { |
| | | super.onDestroy(); |
| | | Log.d(TAG, "Service destroyed"); |
| | | |
| | | // 忢并鿾ç¼ç å¨åæä»¶ä¼ è¾å¨ |
| | | stopEncoder(); |
| | | stopFileTransmitter(); |
| | | } |
| | | |
| | | /** |
| | | * ç¼ç é
置类 |
| | | */ |
| | | private static class EncodeConfig { |
| | | String ip; |
| | | int port; |
| | | int width; |
| | | int height; |
| | | int framerate; |
| | | String simPhone; |
| | | |
| | | // ä»JSONè§£æé
ç½® |
| | | static EncodeConfig fromJson(String jsonConfig) throws JSONException { |
| | | EncodeConfig config = new EncodeConfig(); |
| | | if (jsonConfig == null || jsonConfig.trim().isEmpty()) { |
| | | // 使ç¨é»è®¤å¼ |
| | | config.width = DEFAULT_WIDTH; |
| | | config.height = DEFAULT_HEIGHT; |
| | | config.framerate = DEFAULT_FRAME_RATE; |
| | | config.ip = null; |
| | | config.port = 0; |
| | | config.simPhone = null; |
| | | return config; |
| | | } |
| | | |
| | | JSONObject json = new JSONObject(jsonConfig); |
| | | config.width = json.optInt("width", DEFAULT_WIDTH); |
| | | config.height = json.optInt("height", DEFAULT_HEIGHT); |
| | | config.framerate = json.optInt("framerate", DEFAULT_FRAME_RATE); |
| | | config.ip = json.optString("ip", null); |
| | | config.port = json.optInt("port", 0); |
| | | config.simPhone = json.optString("simPhone", null); |
| | | |
| | | return config; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æä»¶ä¼ è¾é
置类 |
| | | */ |
| | | private static class FileTransmitConfig { |
| | | String ip; |
| | | int port; |
| | | int framerate; |
| | | String simPhone; |
| | | String filePath; // H264æä»¶è·¯å¾ |
| | | int protocolType; // å议类åï¼1-UDPï¼2-TCP |
| | | |
| | | // ä»JSONè§£æé
ç½® |
| | | static FileTransmitConfig fromJson(String jsonConfig) throws JSONException { |
| | | FileTransmitConfig config = new FileTransmitConfig(); |
| | | if (jsonConfig == null || jsonConfig.trim().isEmpty()) { |
| | | throw new JSONException("File transmit config cannot be empty"); |
| | | } |
| | | |
| | | JSONObject json = new JSONObject(jsonConfig); |
| | | config.ip = json.optString("ip", null); |
| | | config.port = json.optInt("port", 0); |
| | | config.framerate = json.optInt("framerate", DEFAULT_FRAME_RATE); |
| | | config.simPhone = json.optString("simPhone", "013120122580"); |
| | | config.filePath = json.optString("filePath", null); |
| | | // å议类åï¼é»è®¤TCPï¼2ï¼ï¼1-UDPï¼2-TCP |
| | | config.protocolType = json.optInt("protocolType", JT1076ProtocolHelper.PROTOCOL_TYPE_TCP); |
| | | |
| | | return config; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ§å¶H264ç¼ç åæä»¶ä¼ è¾ |
| | | * @param action æä½ç±»åï¼ |
| | | * 0-å¼å¯h264æä»¶åå
¥ï¼ |
| | | * 1-忢h264ç¼ç 并忢åå
¥æä»¶ï¼ |
| | | * 2-å¼å¯ç½ç»æ¨éh264ï¼ä¸åå
¥æä»¶ï¼ï¼ |
| | | * 3-忢h264ç¼ç 并忢ç½ç»æ¨éï¼ |
| | | * 4-å¼å§ä¼ è¾H264æä»¶ï¼ä»æä»¶è¯»åå¹¶ç½ç»æ¨éï¼ï¼ |
| | | * 5-忢H264æä»¶ä¼ è¾ |
| | | * @param jsonConfig JSONæ ¼å¼çé
ç½®åæ° |
| | | * action 0/2: å
å«ï¼ipãportãwidthãheightãframerateãsimPhone |
| | | * action 4: å
å«ï¼ipãportãframerateãsimPhoneãfilePathãprotocolTypeï¼å¯éï¼1-UDPï¼2-TCPï¼é»è®¤TCPï¼ |
| | | * action 1/3/5: æ¤åæ°å¯ä¸ºç©ºænull |
| | | * @return 0-æåï¼1-失败 |
| | | */ |
| | | private synchronized int controlEncode(int action, String jsonConfig) { |
| | | Log.d(TAG, "controlEncode called with action: " + action + ", jsonConfig: " + jsonConfig); |
| | | |
| | | try { |
| | | switch (action) { |
| | | case 0: // å¼å¯h264æä»¶åå
¥ |
| | | try { |
| | | EncodeConfig config0 = EncodeConfig.fromJson(jsonConfig); |
| | | return startFileEncode(config0); |
| | | } catch (JSONException e) { |
| | | Log.e(TAG, "Failed to parse JSON config: " + jsonConfig, e); |
| | | return 1; |
| | | } |
| | | |
| | | case 1: // 忢h264ç¼ç 并忢åå
¥æä»¶ |
| | | return stopEncoder(); |
| | | |
| | | case 2: // å¼å¯ç½ç»æ¨éh264ï¼ä¸åå
¥æä»¶ï¼ |
| | | try { |
| | | EncodeConfig config2 = EncodeConfig.fromJson(jsonConfig); |
| | | return startNetworkEncode(config2); |
| | | } catch (JSONException e) { |
| | | Log.e(TAG, "Failed to parse JSON config: " + jsonConfig, e); |
| | | return 1; |
| | | } |
| | | |
| | | case 3: // 忢h264ç¼ç 并忢ç½ç»æ¨é |
| | | return stopEncoder(); |
| | | |
| | | case 4: // å¼å§ä¼ è¾H264æä»¶ |
| | | try { |
| | | FileTransmitConfig config4 = FileTransmitConfig.fromJson(jsonConfig); |
| | | return startFileTransmit(config4); |
| | | } catch (JSONException e) { |
| | | Log.e(TAG, "Failed to parse JSON config: " + jsonConfig, e); |
| | | return 1; |
| | | } |
| | | |
| | | case 5: // 忢H264æä»¶ä¼ è¾ |
| | | return stopFileTransmitter(); |
| | | |
| | | default: |
| | | Log.e(TAG, "Unknown action: " + action); |
| | | return 1; // 失败 |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error in controlEncode", e); |
| | | return 1; // 失败 |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å¯å¨æä»¶ç¼ç 模å¼ï¼åªåå
¥æä»¶ï¼ä¸è¿è¡ç½ç»æ¨éï¼ |
| | | */ |
| | | private int startFileEncode(EncodeConfig config) { |
| | | Log.d(TAG, "Starting file encode mode"); |
| | | |
| | | // 妿ç¼ç å¨å·²ç»å¨è¿è¡ï¼å
忢 |
| | | if (h264Encoder != null) { |
| | | Log.w(TAG, "Encoder is already running, stopping it first"); |
| | | stopEncoder(); |
| | | } |
| | | |
| | | try { |
| | | // å建ç¼ç å¨ |
| | | h264Encoder = new H264Encoder(); |
| | | |
| | | // 设置ç¼ç åæ°ï¼ä½¿ç¨é
ç½®ä¸çåæ°ï¼ |
| | | 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; |
| | | h264Encoder.setEncoderParams(width, height, framerate, DEFAULT_BITRATE); |
| | | |
| | | long timeFile = System.currentTimeMillis(); |
| | | SimpleDateFormat bcdFormat = new SimpleDateFormat("yyMMddHHmmss"); |
| | | String str = bcdFormat.format(timeFile); |
| | | Log.i(TAG,"æä»¶åï¼"+str); |
| | | // 设置è¾åºæä»¶ |
| | | String fileName = "h264_" + timeFile+ ".h264"; |
| | | File outputFile = new File(outputFileDirectory, fileName); |
| | | h264Encoder.setOutputFile(outputFile.getAbsolutePath()); |
| | | h264Encoder.setEnableFileOutput(true); // å¯ç¨æä»¶è¾åº |
| | | |
| | | // ç¦ç¨ç½ç»ä¼ è¾ |
| | | h264Encoder.setEnableNetworkTransmission(false); |
| | | |
| | | // åå§åå¹¶å¯å¨ï¼ä½¿ç¨é
ç½®ä¸çå辨çï¼ |
| | | 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); |
| | | return 0; // æå |
| | | } else { |
| | | Log.e(TAG, "Failed to initialize encoder"); |
| | | h264Encoder = null; |
| | | return 1; // 失败 |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Failed to start file encode", e); |
| | | h264Encoder = null; |
| | | return 1; // 失败 |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å¯å¨ç½ç»æ¨é模å¼ï¼åªè¿è¡ç½ç»æ¨éï¼ä¸åå
¥æä»¶ï¼ |
| | | */ |
| | | private int startNetworkEncode(EncodeConfig config) { |
| | | Log.d(TAG, "Starting network encode mode"); |
| | | |
| | | // 妿ç¼ç å¨å·²ç»å¨è¿è¡ï¼å
忢 |
| | | if (h264Encoder != null) { |
| | | Log.w(TAG, "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"); |
| | | return 1; // 失败 |
| | | } |
| | | |
| | | try { |
| | | // å建ç¼ç å¨ |
| | | h264Encoder = new H264Encoder(); |
| | | |
| | | // 设置ç¼ç åæ°ï¼ä½¿ç¨é
ç½®ä¸çåæ°ï¼ |
| | | |
| | | |
| | | // 设置ç¼ç åæ°ï¼ä½¿ç¨é
ç½®ä¸çåæ°ï¼ |
| | | 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; |
| | | h264Encoder.setEncoderParams(width, height, framerate, DEFAULT_BITRATE); |
| | | |
| | | // ç¦ç¨æä»¶è¾åº |
| | | h264Encoder.setEnableFileOutput(false); |
| | | |
| | | // å¯ç¨ç½ç»ä¼ è¾å¹¶è®¾ç½®æå¡å¨å°å |
| | | h264Encoder.setEnableNetworkTransmission(true); |
| | | h264Encoder.setServerAddress(config.ip, config.port); |
| | | |
| | | // 设置åè®®åæ°ï¼ä½¿ç¨é
ç½®ä¸çsimPhoneï¼å¦ææªæä¾å使ç¨é»è®¤å¼ï¼ |
| | | String simPhone = config.simPhone != null && !config.simPhone.trim().isEmpty() |
| | | ? config.simPhone : "013120122580"; |
| | | h264Encoder.setProtocolParams(simPhone, (byte)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); |
| | | return 0; // æå |
| | | } else { |
| | | Log.e(TAG, "Failed to initialize encoder"); |
| | | h264Encoder = null; |
| | | return 1; // 失败 |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Failed to start network encode", e); |
| | | h264Encoder = null; |
| | | return 1; // 失败 |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 忢ç¼ç å¨ |
| | | */ |
| | | private int stopEncoder() { |
| | | Log.d(TAG, "Stopping encoder"); |
| | | |
| | | if (h264Encoder != null) { |
| | | try { |
| | | h264Encoder.stop(); |
| | | h264Encoder.release(); |
| | | h264Encoder = null; |
| | | Log.d(TAG, "Encoder stopped successfully"); |
| | | return 0; // æå |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error stopping encoder", e); |
| | | h264Encoder = null; |
| | | return 1; // 失败 |
| | | } |
| | | } else { |
| | | Log.w(TAG, "Encoder is not running"); |
| | | return 0; // æåï¼æ²¡æè¿è¡çç¼ç å¨ï¼è§ä¸ºæåï¼ |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å¯å¨æä»¶ä¼ è¾æ¨¡å¼ï¼ä»H264æä»¶è¯»åå¹¶ç½ç»æ¨éï¼ |
| | | */ |
| | | private int startFileTransmit(FileTransmitConfig config) { |
| | | Log.d(TAG, "Starting file transmit mode"); |
| | | |
| | | // 妿æä»¶ä¼ è¾å¨å·²ç»å¨è¿è¡ï¼å
忢 |
| | | if (h264FileTransmitter != null) { |
| | | Log.w(TAG, "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"); |
| | | return 1; // 失败 |
| | | } |
| | | |
| | | if (config.filePath == null || config.filePath.trim().isEmpty()) { |
| | | Log.e(TAG, "File transmit requires valid filePath in config"); |
| | | return 1; // 失败 |
| | | } |
| | | |
| | | try { |
| | | // æ£æ¥æä»¶æ¯å¦åå¨ |
| | | File file = new File(config.filePath); |
| | | if (!file.exists() || !file.isFile()) { |
| | | Log.e(TAG, "File does not exist: " + config.filePath); |
| | | return 1; // 失败 |
| | | } |
| | | |
| | | // å建æä»¶ä¼ è¾å¨ |
| | | h264FileTransmitter = new H264FileTransmitter(); |
| | | |
| | | // 设置æå¡å¨å°å |
| | | h264FileTransmitter.setServerAddress(config.ip, config.port); |
| | | |
| | | // 设置å议类å |
| | | h264FileTransmitter.setProtocolType(config.protocolType); |
| | | |
| | | // 设置åè®®åæ°ï¼SIMå¡å·åé»è¾ééå·ï¼ |
| | | String simPhone = config.simPhone != null && !config.simPhone.trim().isEmpty() |
| | | ? config.simPhone : "013120122580"; |
| | | h264FileTransmitter.setProtocolParams(simPhone, (byte)1); |
| | | |
| | | // 设置帧çï¼ç¨äºè®¡ç®æ¶é´æ³é´éï¼ |
| | | int framerate = config.framerate > 0 ? config.framerate : DEFAULT_FRAME_RATE; |
| | | h264FileTransmitter.setFrameRate(framerate); |
| | | |
| | | // 设置è¿åº¦åè°ï¼å¯éï¼ç¨äºæ¥å¿è¾åºï¼ |
| | | 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 : "")); |
| | | } |
| | | |
| | | @Override |
| | | public void onComplete() { |
| | | Log.d(TAG, "File transmit completed"); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(String error) { |
| | | Log.e(TAG, "File transmit error: " + error); |
| | | } |
| | | }); |
| | | |
| | | // åå§åSocketè¿æ¥ |
| | | if (!h264FileTransmitter.initialize()) { |
| | | Log.e(TAG, "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); |
| | | return 0; // æå |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Failed to start file transmit", e); |
| | | if (h264FileTransmitter != null) { |
| | | try { |
| | | h264FileTransmitter.stop(); |
| | | } catch (Exception ex) { |
| | | Log.e(TAG, "Error stopping file transmitter after failure", ex); |
| | | } |
| | | h264FileTransmitter = null; |
| | | } |
| | | return 1; // 失败 |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 忢æä»¶ä¼ è¾å¨ |
| | | */ |
| | | private int stopFileTransmitter() { |
| | | Log.d(TAG, "Stopping file transmitter"); |
| | | |
| | | if (h264FileTransmitter != null) { |
| | | try { |
| | | h264FileTransmitter.stop(); |
| | | h264FileTransmitter = null; |
| | | Log.d(TAG, "File transmitter stopped successfully"); |
| | | return 0; // æå |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error stopping file transmitter", e); |
| | | h264FileTransmitter = null; |
| | | return 1; // 失败 |
| | | } |
| | | } else { |
| | | Log.w(TAG, "File transmitter is not running"); |
| | | return 0; // æåï¼æ²¡æè¿è¡çæä»¶ä¼ è¾å¨ï¼è§ä¸ºæåï¼ |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è·åèµæºåè¡¨ï¼æ ¹æ®JT/T 1076-2016表23å®ä¹ï¼ |
| | | * @param startTime å¼å§æ¶é´ï¼æ ¼å¼ï¼YYMMDDHHmmssï¼ |
| | | * @param endTime ç»ææ¶é´ï¼æ ¼å¼ï¼YYMMDDHHmmssï¼ |
| | | * @return èµæºå表 |
| | | */ |
| | | private List<ResourceInfo> getResourceList(String startTime, String endTime) { |
| | | Log.d(TAG, "getResourceList called, startTime: " + startTime + ", endTime: " + endTime); |
| | | |
| | | List<ResourceInfo> resourceList = new ArrayList<>(); |
| | | |
| | | try { |
| | | // æ«æè¾åºç®å½ä¸çH264æä»¶ |
| | | File dir = new File(outputFileDirectory); |
| | | if (!dir.exists() || !dir.isDirectory()) { |
| | | Log.w(TAG, "Output directory does not exist: " + 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"); |
| | | return resourceList; |
| | | } |
| | | |
| | | // è§£ææ¶é´èå´ |
| | | Date startDate = parseTime(startTime); |
| | | Date endDate = parseTime(endTime); |
| | | |
| | | if (startDate == null || endDate == null) { |
| | | Log.e(TAG, "Invalid time format, startTime: " + startTime + ", endTime: " + endTime); |
| | | return resourceList; |
| | | } |
| | | |
| | | // éåæä»¶ï¼æ¥æ¾å¨æ¶é´èå´å
çæä»¶ |
| | | for (File file : files) { |
| | | ResourceInfo resourceInfo = createResourceInfoFromFile(file, startDate, endDate); |
| | | if (resourceInfo != null) { |
| | | resourceList.add(resourceInfo); |
| | | } |
| | | } |
| | | |
| | | Log.d(TAG, "Found " + resourceList.size() + " resources in time range"); |
| | | return resourceList; |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error getting resource list", e); |
| | | return resourceList; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 仿件åå»ºèµæºä¿¡æ¯ï¼å¦ææä»¶å¨æ¶é´èå´å
ï¼ |
| | | */ |
| | | private ResourceInfo createResourceInfoFromFile(File file, Date startDate, Date endDate) { |
| | | try { |
| | | // ä»æä»¶åææä»¶ä¿®æ¹æ¶é´è·åæä»¶æ¶é´ |
| | | // è¿éå设æä»¶åå
嫿¶é´æ³ï¼æè
ä½¿ç¨æä»¶ä¿®æ¹æ¶é´ |
| | | Date fileDate = new Date(file.lastModified()); |
| | | |
| | | // æ£æ¥æä»¶æ¶é´æ¯å¦å¨æå®èå´å
|
| | | if (fileDate.before(startDate) || fileDate.after(endDate)) { |
| | | return null; // ä¸å¨æ¶é´èå´å
|
| | | } |
| | | |
| | | // åå»ºèµæºä¿¡æ¯å¯¹è±¡ |
| | | ResourceInfo resourceInfo = new ResourceInfo(); |
| | | |
| | | // é»è¾ééå·ï¼é»è®¤å¼ï¼å®é
åºä»é
ç½®è·åï¼ |
| | | 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.setAlarmFlag(0L); |
| | | |
| | | // é³è§é¢èµæºç±»åï¼2-è§é¢ |
| | | resourceInfo.setResourceType((byte) 2); |
| | | |
| | | // ç æµç±»åï¼1-ä¸»ç æµ |
| | | resourceInfo.setStreamType((byte) 1); |
| | | |
| | | // åå¨å¨ç±»åï¼1-主åå¨å¨ |
| | | resourceInfo.setStorageType((byte) 1); |
| | | |
| | | // æä»¶å¤§å° |
| | | resourceInfo.setFileSize(file.length()); |
| | | |
| | | return resourceInfo; |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error creating resource info from file: " + file.getName(), e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è§£æBCDæ¶é´å符串 |
| | | * @param timeStr æ¶é´åç¬¦ä¸²ï¼æ ¼å¼ï¼YYMMDDHHmmssï¼ |
| | | * @return Date对象ï¼å¦æè§£æå¤±è´¥è¿ånull |
| | | */ |
| | | private Date parseTime(String timeStr) { |
| | | if (timeStr == null || timeStr.length() != 12) { |
| | | return null; |
| | | } |
| | | |
| | | try { |
| | | SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmss", Locale.CHINA); |
| | | return format.parse(timeStr); |
| | | } catch (ParseException e) { |
| | | Log.e(TAG, "Failed to parse time: " + timeStr, e); |
| | | return null; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | * // 设置UDPæå¡å¨å°åï¼å¯éï¼ |
| | | * encoder.setServerAddress("192.168.1.100", 8888); |
| | | * encoder.setProtocolParams("123456789012", (byte)1); |
| | | * encoder.setEnableNetworkTransmission(true); // å¯ç¨TCP/UDPç½ç»ä¼ è¾ï¼false表示ç¦ç¨ |
| | | * |
| | | * // åå§åå¹¶å¯å¨ |
| | | * int[] cameraIdRange = {0, 0}; |
| | |
| | | private boolean enableFileOutput = false; // æ¯å¦å¯ç¨æä»¶è¾åº |
| | | private boolean spsPpsWritten = false; // æ è®°SPS/PPSæ¯å¦å·²åå
¥ |
| | | |
| | | // ç½ç»ä¼ è¾æ§å¶ |
| | | private boolean enableNetworkTransmission = true; // æ¯å¦å¯ç¨TCP/UDPç½ç»ä¼ è¾ |
| | | |
| | | // ç¼ç åè° |
| | | public interface OnFrameEncodedCallback { |
| | | void onFrameEncoded(byte[] data, boolean isKeyFrame); |
| | |
| | | public H264Encoder() { |
| | | this.usbCamera = new UsbCamera(); |
| | | this.protocolHelper = new JT1076ProtocolHelper(); |
| | | protocolHelper.setProtocolType(JT1076ProtocolHelper.PROTOCOL_TYPE_TCP);//设置为tcpä¼ è¾ |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 设置æ¯å¦å¯ç¨TCP/UDPç½ç»ä¼ è¾ |
| | | * @param enable true表示å¯ç¨ç½ç»ä¼ è¾ï¼false表示ç¦ç¨ |
| | | */ |
| | | public void setEnableNetworkTransmission(boolean enable) { |
| | | this.enableNetworkTransmission = enable; |
| | | Log.d(TAG, "Network transmission " + (enable ? "enabled" : "disabled")); |
| | | } |
| | | |
| | | /** |
| | | * åå§åæå头åç¼ç å¨ |
| | | */ |
| | | public boolean initialize(int[] cameraIdRange, String cameraName, int[] resolution, boolean ayCamera) { |
| | |
| | | // 3. åå§åH264ç¼ç å¨ |
| | | initEncoder(); |
| | | |
| | | // 4. åå§åUDP Socket |
| | | if (!protocolHelper.initializeUdpSocket()) { |
| | | return false; |
| | | // 4. åå§åSocketï¼UDPæTCPï¼æ ¹æ®å议类åèªå¨éæ©ï¼ |
| | | // åªæå¨å¯ç¨ç½ç»ä¼ è¾æ¶æåå§åSocket |
| | | if (enableNetworkTransmission) { |
| | | if (!protocolHelper.initializeSocket()) { |
| | | return false; |
| | | } |
| | | } else { |
| | | Log.d(TAG, "Network transmission disabled, skipping socket initialization"); |
| | | } |
| | | |
| | | // 5. åå§åæä»¶è¾åºï¼ä»
å建æä»¶ï¼SPS/PPSå¨ç¬¬ä¸æ¬¡è¾åºæ¶åå
¥ï¼ |
| | |
| | | int outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0); |
| | | while (outputBufferIndex >= 0) { |
| | | ByteBuffer outputBuffer = encoder.getOutputBuffer(outputBufferIndex); |
| | | Log.i(TAG,"1111"); |
| | | if (outputBuffer != null && bufferInfo.size > 0) { |
| | | // æ£æ¥æ¯å¦ä¸ºå
³é®å¸§ |
| | | boolean isKeyFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0; |
| | | Log.i(TAG,"2222"); |
| | | // å¤å¶ç¼ç æ°æ® |
| | | byte[] encodedData = new byte[bufferInfo.size]; |
| | | outputBuffer.position(bufferInfo.offset); |
| | |
| | | * åéç¼ç åçæ°æ®ï¼æJT/T 1076-2016åè®®æå
ï¼ |
| | | */ |
| | | private void sendEncodedData(byte[] data, long timestamp, boolean isKeyFrame) { |
| | | // 妿æªå¯ç¨ç½ç»ä¼ è¾ï¼ç´æ¥è¿å |
| | | if (!enableNetworkTransmission) { |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // è®¡ç®æ¶é´é´é |
| | | long currentTime = System.currentTimeMillis(); |
| | |
| | | packetData, timestamp, dataType, packetMark, |
| | | lastIFrameInterval, lastFrameInterval); |
| | | |
| | | // åéUDPå
|
| | | protocolHelper.sendUdpPacket(rtpPacket); |
| | | // åéRTPå
ï¼UDPæTCPï¼æ ¹æ®å议类åèªå¨éæ©ï¼ |
| | | protocolHelper.sendPacket(rtpPacket); |
| | | |
| | | offset += packetDataSize; |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | // å
³éUDP Socket |
| | | if (protocolHelper != null) { |
| | | protocolHelper.closeUdpSocket(); |
| | | // å
³éSocketï¼UDPæTCPï¼æ ¹æ®å议类åèªå¨éæ©ï¼ |
| | | // åªæå¨å¯ç¨ç½ç»ä¼ è¾æ¶æéè¦å
³éSocket |
| | | if (enableNetworkTransmission && protocolHelper != null) { |
| | | protocolHelper.closeSocket(); |
| | | } |
| | | |
| | | // å
³éæä»¶è¾åº |
| New file |
| | |
| | | package com.anyun.h264; |
| | | |
| | | import android.util.Log; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.IOException; |
| | | import java.util.Arrays; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | /** |
| | | * H264æä»¶ä¼ è¾å¨ |
| | | * ä»H264æä»¶è¯»åæ°æ®ï¼æç
§JT/T 1076-2016åè®®éè¿TCP/UDPä¼ è¾ |
| | | * |
| | | * 使ç¨ç¤ºä¾ï¼ |
| | | * <pre> |
| | | * // åå»ºä¼ è¾å¨ |
| | | * H264FileTransmitter transmitter = new H264FileTransmitter(); |
| | | * |
| | | * // 设置æå¡å¨å°ååå议类å |
| | | * transmitter.setServerAddress("192.168.1.100", 8888); |
| | | * transmitter.setProtocolType(JT1076ProtocolHelper.PROTOCOL_TYPE_TCP); // æ PROTOCOL_TYPE_UDP |
| | | * |
| | | * // 设置åè®®åæ° |
| | | * transmitter.setProtocolParams("013120122580", (byte)1); |
| | | * |
| | | * // 设置帧çï¼ç¨äºè®¡ç®æ¶é´æ³é´éï¼ |
| | | * transmitter.setFrameRate(25); |
| | | * |
| | | * // åå§åSocket |
| | | * if (transmitter.initialize()) { |
| | | * // å¼å§ä¼ è¾æä»¶ |
| | | * transmitter.transmitFile("/path/to/video.h264"); |
| | | * } |
| | | * |
| | | * // åæ¢ä¼ è¾ |
| | | * transmitter.stop(); |
| | | * </pre> |
| | | */ |
| | | public class H264FileTransmitter { |
| | | private static final String TAG = "H264FileTransmitter"; |
| | | |
| | | // H264 NALèµ·å§ç |
| | | private static final byte[] START_CODE_3 = {0x00, 0x00, 0x01}; |
| | | private static final byte[] START_CODE_4 = {0x00, 0x00, 0x00, 0x01}; |
| | | |
| | | // JT/T 1076-2016 å议工å
·ç±» |
| | | private JT1076ProtocolHelper protocolHelper; |
| | | |
| | | // ä¼ è¾æ§å¶ |
| | | private AtomicBoolean isRunning = new AtomicBoolean(false); |
| | | private Thread transmitThread; |
| | | |
| | | // åæ°é
ç½® |
| | | private int frameRate = 25; // 帧çï¼ç¨äºè®¡ç®æ¶é´æ³é´é |
| | | private long frameInterval = 1000 / 25; // 帧é´éï¼æ¯«ç§ï¼ |
| | | |
| | | // æ¶é´æ³ç®¡ç |
| | | private long lastIFrameTime = 0; // ä¸ä¸ä¸ªI帧æ¶é´ |
| | | private long lastFrameTime = 0; // ä¸ä¸å¸§æ¶é´ |
| | | private long baseTimestamp = 0; // åºåæ¶é´æ³ |
| | | |
| | | /** |
| | | * ä¼ è¾è¿åº¦åè°æ¥å£ |
| | | */ |
| | | public interface OnTransmitProgressCallback { |
| | | /** |
| | | * ä¼ è¾è¿åº¦åè° |
| | | * @param currentFrame å½å帧åºå·ï¼ä»1å¼å§ï¼ |
| | | * @param totalFrames æ»å¸§æ°ï¼å¦ææªç¥å为-1ï¼ |
| | | */ |
| | | void onProgress(int currentFrame, int totalFrames); |
| | | |
| | | /** |
| | | * ä¼ è¾å®æåè° |
| | | */ |
| | | void onComplete(); |
| | | |
| | | /** |
| | | * ä¼ è¾é误åè° |
| | | * @param error éè¯¯ä¿¡æ¯ |
| | | */ |
| | | void onError(String error); |
| | | } |
| | | private OnTransmitProgressCallback progressCallback; |
| | | |
| | | public H264FileTransmitter() { |
| | | this.protocolHelper = new JT1076ProtocolHelper(); |
| | | // é»è®¤ä½¿ç¨TCPåè®® |
| | | protocolHelper.setProtocolType(JT1076ProtocolHelper.PROTOCOL_TYPE_TCP); |
| | | } |
| | | |
| | | /** |
| | | * 设置æå¡å¨å°å |
| | | */ |
| | | public void setServerAddress(String ip, int port) { |
| | | protocolHelper.setServerAddress(ip, port); |
| | | } |
| | | |
| | | /** |
| | | * è®¾ç½®ä¼ è¾å议类åï¼UDPæTCPï¼ |
| | | * @param protocolType PROTOCOL_TYPE_UDP æ PROTOCOL_TYPE_TCP |
| | | */ |
| | | public void setProtocolType(int protocolType) { |
| | | protocolHelper.setProtocolType(protocolType); |
| | | } |
| | | |
| | | /** |
| | | * 设置SIMå¡å·åé»è¾ééå· |
| | | */ |
| | | public void setProtocolParams(String simCardNumber, byte logicalChannelNumber) { |
| | | protocolHelper.setProtocolParams(simCardNumber, logicalChannelNumber); |
| | | } |
| | | |
| | | /** |
| | | * 设置帧çï¼ç¨äºè®¡ç®æ¶é´æ³é´éï¼ |
| | | * @param frameRate 帧çï¼fpsï¼ |
| | | */ |
| | | 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"); |
| | | } |
| | | |
| | | /** |
| | | * è®¾ç½®ä¼ è¾è¿åº¦åè° |
| | | */ |
| | | public void setOnTransmitProgressCallback(OnTransmitProgressCallback callback) { |
| | | this.progressCallback = callback; |
| | | } |
| | | |
| | | /** |
| | | * åå§åSocketè¿æ¥ |
| | | * @return æ¯å¦æå |
| | | */ |
| | | public boolean initialize() { |
| | | if (isRunning.get()) { |
| | | Log.w(TAG, "Transmitter is already running"); |
| | | return false; |
| | | } |
| | | |
| | | if (!protocolHelper.initializeSocket()) { |
| | | Log.e(TAG, "Failed to initialize socket"); |
| | | return false; |
| | | } |
| | | |
| | | // éç½®åºå· |
| | | protocolHelper.resetSequenceNumber(); |
| | | |
| | | // åå§åæ¶é´æ³ |
| | | baseTimestamp = System.currentTimeMillis(); |
| | | lastIFrameTime = 0; |
| | | lastFrameTime = 0; |
| | | |
| | | Log.d(TAG, "Socket initialized successfully"); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * å¼å§ä¼ è¾H264æä»¶ |
| | | * @param filePath H264æä»¶è·¯å¾ |
| | | */ |
| | | public void transmitFile(String filePath) { |
| | | if (isRunning.get()) { |
| | | Log.w(TAG, "Transmitter is already running"); |
| | | return; |
| | | } |
| | | |
| | | File file = new File(filePath); |
| | | if (!file.exists() || !file.isFile()) { |
| | | Log.e(TAG, "File does not exist: " + filePath); |
| | | if (progressCallback != null) { |
| | | progressCallback.onError("File does not exist: " + filePath); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | isRunning.set(true); |
| | | |
| | | // å¯å¨ä¼ è¾çº¿ç¨ |
| | | transmitThread = new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | transmitFileInternal(file); |
| | | } |
| | | }); |
| | | transmitThread.start(); |
| | | |
| | | Log.d(TAG, "Started transmitting file: " + filePath); |
| | | } |
| | | |
| | | /** |
| | | * ä¼ è¾æä»¶çå
é¨å®ç° |
| | | */ |
| | | private void transmitFileInternal(File file) { |
| | | FileInputStream fis = null; |
| | | int frameCount = 0; |
| | | |
| | | try { |
| | | fis = new FileInputStream(file); |
| | | |
| | | // è¯»åæ´ä¸ªæä»¶å°å
å |
| | | // 注æï¼å¯¹äºå¤§æä»¶ï¼å¯ä»¥æ¹ä¸ºæµå¼è¯»åï¼ä½ä¸ºç®åå®ç°ï¼è¿é使ç¨ä¸æ¬¡æ§è¯»å |
| | | long fileSize = file.length(); |
| | | if (fileSize > Integer.MAX_VALUE) { |
| | | throw new IOException("File too large: " + fileSize + " bytes"); |
| | | } |
| | | |
| | | byte[] fileData = new byte[(int) fileSize]; |
| | | int bytesRead = 0; |
| | | int totalRead = 0; |
| | | while (totalRead < fileData.length && (bytesRead = fis.read(fileData, totalRead, fileData.length - totalRead)) > 0) { |
| | | totalRead += bytesRead; |
| | | } |
| | | |
| | | if (totalRead != fileData.length) { |
| | | Log.w(TAG, "File read incomplete, expected: " + fileData.length + ", actual: " + totalRead); |
| | | } |
| | | |
| | | Log.d(TAG, "File read complete, size: " + fileData.length + " bytes"); |
| | | |
| | | // æå¸§è§£æå¹¶ä¼ è¾ï¼ä¸ä¸ªå¸§å
å«ä»ä¸ä¸ªèµ·å§ç å°ä¸ä¸ä¸ªèµ·å§ç ä¹é´çæææ°æ®ï¼å
æ¬èµ·å§ç ï¼ |
| | | int offset = 0; |
| | | while (offset < fileData.length && isRunning.get()) { |
| | | // æ¥æ¾ä¸ä¸ä¸ªå¸§çèµ·å§ä½ç½® |
| | | int nextFrameStart = findNextFrameStart(fileData, offset); |
| | | if (nextFrameStart < 0) { |
| | | // æ²¡ææ¾å°ä¸ä¸ä¸ªèµ·å§ç ï¼å½åoffsetå°æä»¶æ«å°¾æ¯ä¸ä¸ªå®æ´ç帧 |
| | | if (offset < fileData.length) { |
| | | byte[] frameData = Arrays.copyOfRange(fileData, offset, fileData.length); |
| | | transmitFrame(frameData, frameCount); |
| | | frameCount++; |
| | | |
| | | // éç¥è¿åº¦ |
| | | if (progressCallback != null) { |
| | | progressCallback.onProgress(frameCount, -1); |
| | | } |
| | | } |
| | | break; |
| | | } |
| | | |
| | | // æåå½åå¸§æ°æ®ï¼å
å«èµ·å§ç ï¼ |
| | | if (nextFrameStart > offset) { |
| | | byte[] frameData = Arrays.copyOfRange(fileData, offset, nextFrameStart); |
| | | transmitFrame(frameData, frameCount); |
| | | frameCount++; |
| | | |
| | | // éç¥è¿åº¦ |
| | | if (progressCallback != null) { |
| | | progressCallback.onProgress(frameCount, -1); |
| | | } |
| | | } |
| | | |
| | | // ç§»å¨å°ä¸ä¸ä¸ªå¸§çèµ·å§ä½ç½® |
| | | offset = nextFrameStart; |
| | | } |
| | | |
| | | Log.d(TAG, "Transmission complete, total frames: " + frameCount); |
| | | |
| | | if (progressCallback != null) { |
| | | progressCallback.onComplete(); |
| | | } |
| | | |
| | | } catch (IOException e) { |
| | | Log.e(TAG, "Error transmitting file", e); |
| | | if (progressCallback != null) { |
| | | progressCallback.onError("IO Error: " + e.getMessage()); |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Unexpected error during transmission", e); |
| | | if (progressCallback != null) { |
| | | progressCallback.onError("Error: " + e.getMessage()); |
| | | } |
| | | } finally { |
| | | if (fis != null) { |
| | | try { |
| | | fis.close(); |
| | | } catch (IOException e) { |
| | | Log.e(TAG, "Error closing file", e); |
| | | } |
| | | } |
| | | isRunning.set(false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ¥æ¾ä¸ä¸ä¸ªå¸§çèµ·å§ä½ç½®ï¼ä¸ä¸ä¸ªèµ·å§ç çä½ç½®ï¼ |
| | | * @param data æä»¶æ°æ® |
| | | * @param currentFrameStart å½å帧çèµ·å§ä½ç½®ï¼èµ·å§ç ä½ç½®ï¼ |
| | | * @return ä¸ä¸ä¸ªå¸§çèµ·å§ç ä½ç½®ï¼å¦ææªæ¾å°è¿å-1 |
| | | */ |
| | | private int findNextFrameStart(byte[] data, int currentFrameStart) { |
| | | if (currentFrameStart >= data.length) { |
| | | return -1; |
| | | } |
| | | |
| | | // è·³è¿å½å帧çèµ·å§ç |
| | | int offset = currentFrameStart; |
| | | if (isStartCodeAt(data, offset)) { |
| | | offset += getStartCodeLength(data, offset); |
| | | } |
| | | |
| | | // æ¥æ¾ä¸ä¸ä¸ªèµ·å§ç |
| | | for (int i = offset; i < data.length - 3; i++) { |
| | | if (isStartCodeAt(data, i)) { |
| | | return i; |
| | | } |
| | | } |
| | | |
| | | return -1; |
| | | } |
| | | |
| | | /** |
| | | * æ£æ¥æå®ä½ç½®æ¯å¦ä¸ºèµ·å§ç |
| | | */ |
| | | private boolean isStartCodeAt(byte[] data, int offset) { |
| | | if (offset + 3 > data.length) { |
| | | return false; |
| | | } |
| | | |
| | | // æ£æ¥4åèèµ·å§ç |
| | | if (offset + 4 <= data.length) { |
| | | if (data[offset] == 0x00 && data[offset + 1] == 0x00 && |
| | | data[offset + 2] == 0x00 && data[offset + 3] == 0x01) { |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | // æ£æ¥3åèèµ·å§ç ï¼ç¡®ä¿åé¢ä¸æ¯0x00ï¼ |
| | | if (offset == 0 || data[offset - 1] != 0x00) { |
| | | if (data[offset] == 0x00 && data[offset + 1] == 0x00 && |
| | | data[offset + 2] == 0x01) { |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * è·åèµ·å§ç é¿åº¦ |
| | | */ |
| | | private int getStartCodeLength(byte[] data, int offset) { |
| | | if (offset + 4 <= data.length && |
| | | data[offset] == 0x00 && data[offset + 1] == 0x00 && |
| | | data[offset + 2] == 0x00 && data[offset + 3] == 0x01) { |
| | | return 4; |
| | | } |
| | | return 3; |
| | | } |
| | | |
| | | /** |
| | | * å¤æå¸§æ°æ®æ¯å¦ä¸ºI帧ï¼IDRï¼ |
| | | * @param frameData å¸§æ°æ®ï¼å
å«èµ·å§ç ï¼ |
| | | * @return æ¯å¦ä¸ºI帧 |
| | | */ |
| | | private boolean isIFrame(byte[] frameData) { |
| | | if (frameData == null || frameData.length < 5) { |
| | | return false; |
| | | } |
| | | |
| | | // æ¥æ¾å¸§ä¸ææçNALåå
ï¼æ£æ¥æ¯å¦å
å«IDR帧 |
| | | int offset = 0; |
| | | boolean hasSpsPps = false; |
| | | |
| | | while (offset < frameData.length - 3) { |
| | | // æ£æ¥å½åä½ç½®æ¯å¦ä¸ºèµ·å§ç |
| | | if (isStartCodeAt(frameData, offset)) { |
| | | // è·³è¿èµ·å§ç |
| | | int startCodeLen = getStartCodeLength(frameData, offset); |
| | | int nalStart = offset + startCodeLen; |
| | | |
| | | if (nalStart < frameData.length) { |
| | | // è·åNALç±»åï¼ç¬¬ä¸ä¸ªåèçä½5ä½ï¼ |
| | | int nalType = frameData[nalStart] & 0x1F; |
| | | |
| | | // NALç±»å5 = IDR (Instantaneous Decoder Refresh) å
³é®å¸§ |
| | | if (nalType == 5) { |
| | | return true; // æ¾å°IDR帧ï¼ç¡®å®æ¯I帧 |
| | | } |
| | | |
| | | // NALç±»å7 = SPS, ç±»å8 = PPS |
| | | if (nalType == 7 || nalType == 8) { |
| | | hasSpsPps = true; |
| | | } |
| | | } |
| | | |
| | | // ç§»å¨å°ä¸ä¸ä¸ªå¯è½çä½ç½®ç»§ç»æ¥æ¾ |
| | | offset = nalStart + 1; |
| | | } else { |
| | | offset++; |
| | | } |
| | | } |
| | | |
| | | // å¦ææ²¡ææ¾å°IDRï¼ä½å
å«SPS/PPSï¼ä¹è®¤ä¸ºæ¯å
³é®å¸§ç¸å
³çæ°æ® |
| | | // ï¼æäºç¼ç å¨å¯è½å°SPS/PPSåç¬ä½ä¸ºä¸ä¸ª"帧"åéï¼ |
| | | return hasSpsPps; |
| | | } |
| | | |
| | | /** |
| | | * ä¼ è¾ä¸ä¸ªå®æ´çå¸§æ°æ®ï¼ç±»ä¼¼H264Encoderçæ¹å¼ï¼ |
| | | * @param frameData å¸§æ°æ®ï¼å
å«èµ·å§ç ï¼ä¸H264Encoderè¾åºçæ ¼å¼ä¸è´ï¼ |
| | | * @param frameIndex 帧åºå· |
| | | */ |
| | | private void transmitFrame(byte[] frameData, int frameIndex) { |
| | | if (frameData == null || frameData.length == 0) { |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // å¤æå¸§ç±»å |
| | | boolean isKeyFrame = isIFrame(frameData); |
| | | |
| | | // è®¡ç®æ¶é´æ³ |
| | | long timestamp = baseTimestamp + (frameIndex * frameInterval); |
| | | |
| | | // è®¡ç®æ¶é´é´é |
| | | long lastIFrameInterval = (lastIFrameTime > 0) ? (timestamp - lastIFrameTime) : 0; |
| | | long lastFrameInterval = (lastFrameTime > 0) ? (timestamp - lastFrameTime) : frameInterval; |
| | | |
| | | if (isKeyFrame) { |
| | | lastIFrameTime = timestamp; |
| | | } |
| | | lastFrameTime = timestamp; |
| | | |
| | | // å¤æå¸§ç±»åï¼ç¨äºåè®®ï¼ |
| | | int dataType = isKeyFrame ? JT1076ProtocolHelper.DATA_TYPE_I_FRAME : |
| | | JT1076ProtocolHelper.DATA_TYPE_P_FRAME; |
| | | |
| | | // åå
åéï¼å¦ææ°æ®è¶
è¿æå¤§å
大å°ï¼ |
| | | int offset = 0; |
| | | int totalPackets = (int) Math.ceil((double) frameData.length / JT1076ProtocolHelper.MAX_PACKET_SIZE); |
| | | |
| | | for (int i = 0; i < totalPackets; i++) { |
| | | int packetDataSize = Math.min(JT1076ProtocolHelper.MAX_PACKET_SIZE, frameData.length - offset); |
| | | byte[] packetData = Arrays.copyOfRange(frameData, offset, offset + packetDataSize); |
| | | |
| | | // ç¡®å®åå
æ è®° |
| | | int packetMark; |
| | | if (totalPackets == 1) { |
| | | packetMark = JT1076ProtocolHelper.PACKET_MARK_ATOMIC; |
| | | } else if (i == 0) { |
| | | packetMark = JT1076ProtocolHelper.PACKET_MARK_FIRST; |
| | | } else if (i == totalPackets - 1) { |
| | | packetMark = JT1076ProtocolHelper.PACKET_MARK_LAST; |
| | | } else { |
| | | packetMark = JT1076ProtocolHelper.PACKET_MARK_MIDDLE; |
| | | } |
| | | |
| | | // å建RTPå
|
| | | byte[] rtpPacket = protocolHelper.createVideoRtpPacket( |
| | | packetData, timestamp, dataType, packetMark, |
| | | lastIFrameInterval, lastFrameInterval); |
| | | |
| | | // åéRTPå
ï¼UDPæTCPï¼æ ¹æ®å议类åèªå¨éæ©ï¼ |
| | | protocolHelper.sendPacket(rtpPacket); |
| | | |
| | | offset += packetDataSize; |
| | | } |
| | | |
| | | // æ§å¶åééçï¼æ¨¡æå¸§çï¼ |
| | | if (frameInterval > 0) { |
| | | try { |
| | | Thread.sleep(frameInterval); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | Log.d(TAG, "Transmission interrupted"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Error transmitting frame", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * åæ¢ä¼ è¾ |
| | | */ |
| | | public void stop() { |
| | | if (!isRunning.get()) { |
| | | return; |
| | | } |
| | | |
| | | isRunning.set(false); |
| | | |
| | | // çå¾
ä¼ è¾çº¿ç¨ç»æ |
| | | if (transmitThread != null) { |
| | | try { |
| | | transmitThread.join(2000); |
| | | } catch (InterruptedException e) { |
| | | Log.e(TAG, "Wait transmit thread error", e); |
| | | } |
| | | } |
| | | |
| | | // å
³éSocket |
| | | if (protocolHelper != null) { |
| | | protocolHelper.closeSocket(); |
| | | } |
| | | |
| | | Log.d(TAG, "H264 file transmitter stopped"); |
| | | } |
| | | |
| | | /** |
| | | * éæ¾èµæº |
| | | */ |
| | | public void release() { |
| | | stop(); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | /** |
| | | * JT/T 1076-2016 å议工å
·ç±» |
| | | * æä¾UDPåéãSIMå¡å·BCD转æ¢ãRTPå
å建çå
Œ
񆊫 |
| | | * æä¾UDP/TCPåéãSIMå¡å·BCD转æ¢ãRTPå
å建çå
Œ
񆊫 |
| | | */ |
| | | public class JT1076ProtocolHelper { |
| | | private static final String TAG = "JT1076ProtocolHelper"; |
| | |
| | | public static final int RTP_PAYLOAD_TYPE_VIDEO = 96; // è§é¢è´è½½ç±»å |
| | | public static final int RTP_PAYLOAD_TYPE_AUDIO = 97; // é³é¢è´è½½ç±»å |
| | | |
| | | // UDPåæ° |
| | | // ä¼ è¾å议类å |
| | | public static final int PROTOCOL_TYPE_UDP = 0; // UDPåè®® |
| | | public static final int PROTOCOL_TYPE_TCP = 1; // TCPåè®® |
| | | |
| | | // æå¡å¨åæ° |
| | | private String serverIp; |
| | | private int serverPort; |
| | | |
| | | // å议类åï¼é»è®¤UDPï¼ |
| | | private int protocolType = PROTOCOL_TYPE_UDP; |
| | | |
| | | // UDPåæ° |
| | | private DatagramSocket udpSocket; |
| | | private InetAddress serverAddress; |
| | | |
| | | // TCPåæ° |
| | | private JT1076TcpClient tcpClient; |
| | | |
| | | // RTPåè®®åæ° |
| | | private String simCardNumber = "123456789012"; // 12ä½SIMå¡å· |
| | |
| | | private short sequenceNumber = 0; // å
åºå·ï¼èªå¨éå¢ï¼ |
| | | |
| | | /** |
| | | * 设置UDPæå¡å¨å°å |
| | | * 设置æå¡å¨å°å |
| | | */ |
| | | public void setServerAddress(String ip, int port) { |
| | | this.serverIp = ip; |
| | | this.serverPort = port; |
| | | // 妿TCP客æ·ç«¯å·²åå¨ï¼æ´æ°å°å |
| | | if (tcpClient != null) { |
| | | tcpClient.setServerAddress(ip, port); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è®¾ç½®ä¼ è¾å议类åï¼UDPæTCPï¼ |
| | | * @param protocolType PROTOCOL_TYPE_UDP æ PROTOCOL_TYPE_TCP |
| | | */ |
| | | 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; |
| | | } |
| | | |
| | | // 妿åè®®ç±»åæ¹åï¼å
å
³éæ§çè¿æ¥ |
| | | if (this.protocolType != protocolType) { |
| | | if (this.protocolType == PROTOCOL_TYPE_UDP) { |
| | | closeUdpSocket(); |
| | | } else { |
| | | closeTcpSocket(); |
| | | } |
| | | } |
| | | |
| | | this.protocolType = protocolType; |
| | | Log.d(TAG, "Protocol type set to: " + (protocolType == PROTOCOL_TYPE_UDP ? "UDP" : "TCP")); |
| | | } |
| | | |
| | | /** |
| | | * è·åå½åå议类å |
| | | */ |
| | | public int getProtocolType() { |
| | | return protocolType; |
| | | } |
| | | |
| | | /** |
| | |
| | | public void setProtocolParams(String simCardNumber, byte logicalChannelNumber) { |
| | | this.simCardNumber = simCardNumber; |
| | | this.logicalChannelNumber = logicalChannelNumber; |
| | | } |
| | | |
| | | /** |
| | | * åå§åSocketï¼æ ¹æ®å议类åèªå¨éæ©UDPæTCPï¼ |
| | | */ |
| | | public boolean initializeSocket() { |
| | | if (protocolType == PROTOCOL_TYPE_UDP) { |
| | | return initializeUdpSocket(); |
| | | } else { |
| | | return initializeTcpSocket(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * åå§åTCP Socket |
| | | */ |
| | | public boolean initializeTcpSocket() { |
| | | try { |
| | | if (serverIp == null || serverIp.isEmpty()) { |
| | | Log.e(TAG, "Server IP not set"); |
| | | return false; |
| | | } |
| | | |
| | | if (tcpClient == null) { |
| | | tcpClient = new JT1076TcpClient(); |
| | | tcpClient.setServerAddress(serverIp, serverPort); |
| | | |
| | | // è®¾ç½®è¿æ¥ç¶æçå¬å¨ |
| | | tcpClient.setConnectionListener(new JT1076TcpClient.ConnectionListener() { |
| | | @Override |
| | | public void onConnected() { |
| | | Log.d(TAG, "TCP connection established"); |
| | | } |
| | | |
| | | @Override |
| | | public void onDisconnected() { |
| | | Log.d(TAG, "TCP connection disconnected"); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Throwable cause) { |
| | | Log.e(TAG, "TCP connection error", cause); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | tcpClient.connect(); |
| | | Log.d(TAG, "TCP socket initializing, target: " + serverIp + ":" + serverPort); |
| | | return true; |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Initialize TCP socket failed", e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å
³éSocketï¼æ ¹æ®å议类åèªå¨éæ©ï¼ |
| | | */ |
| | | public void closeSocket() { |
| | | if (protocolType == PROTOCOL_TYPE_UDP) { |
| | | closeUdpSocket(); |
| | | } else { |
| | | closeTcpSocket(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å
³éUDP Socket |
| | | */ |
| | | public void closeUdpSocket() { |
| | |
| | | } |
| | | |
| | | /** |
| | | * åéUDPå
|
| | | * å
³éTCP Socket |
| | | */ |
| | | public void closeTcpSocket() { |
| | | if (tcpClient != null) { |
| | | tcpClient.disconnect(); |
| | | tcpClient = null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * åéRTPå
ï¼æ ¹æ®å议类åèªå¨éæ©UDPæTCPï¼ |
| | | */ |
| | | public void sendPacket(byte[] packet) { |
| | | if (protocolType == PROTOCOL_TYPE_UDP) { |
| | | sendUdpPacket(packet); |
| | | } else { |
| | | sendTcpPacket(packet); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * åéUDPå
ï¼ä¿æååå
¼å®¹ï¼ |
| | | */ |
| | | public void sendUdpPacket(byte[] packet) { |
| | | try { |
| | |
| | | } |
| | | |
| | | /** |
| | | * åéTCPå
|
| | | */ |
| | | public void sendTcpPacket(byte[] packet) { |
| | | if (tcpClient != null && tcpClient.isConnected()) { |
| | | tcpClient.sendPacket(packet); |
| | | } else { |
| | | Log.w(TAG, "TCP socket not connected"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å°SIMå¡å·è½¬æ¢ä¸ºBCDæ ¼å¼ï¼6åèï¼ |
| | | */ |
| | | public byte[] convertSimToBCD(String simNumber) { |
| New file |
| | |
| | | package com.anyun.h264; |
| | | |
| | | import android.util.Log; |
| | | |
| | | import com.anyun.h264.util.BytesUtils; |
| | | |
| | | import io.netty.bootstrap.Bootstrap; |
| | | import io.netty.buffer.ByteBuf; |
| | | import io.netty.buffer.Unpooled; |
| | | import io.netty.channel.Channel; |
| | | import io.netty.channel.ChannelFuture; |
| | | import io.netty.channel.ChannelFutureListener; |
| | | import io.netty.channel.ChannelHandlerContext; |
| | | 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 java.util.concurrent.TimeUnit; |
| | | |
| | | /** |
| | | * JT/T 1076-2016 TCP客æ·ç«¯å·¥å
·ç±» |
| | | * 使ç¨Nettyå®ç°TCPè¿æ¥åRTPå
åé |
| | | */ |
| | | public class JT1076TcpClient { |
| | | private static final String TAG = "JT1076TcpClient"; |
| | | |
| | | private String serverIp; |
| | | private int serverPort; |
| | | private EventLoopGroup workerGroup; |
| | | private Channel channel; |
| | | private boolean isConnected = false; |
| | | |
| | | // è¿æ¥ç¶æåè°æ¥å£ |
| | | public interface ConnectionListener { |
| | | void onConnected(); |
| | | void onDisconnected(); |
| | | void onError(Throwable cause); |
| | | } |
| | | |
| | | private ConnectionListener connectionListener; |
| | | |
| | | /** |
| | | * 设置æå¡å¨å°å |
| | | */ |
| | | public void setServerAddress(String ip, int port) { |
| | | this.serverIp = ip; |
| | | this.serverPort = port; |
| | | } |
| | | |
| | | /** |
| | | * è®¾ç½®è¿æ¥ç¶æçå¬å¨ |
| | | */ |
| | | public void setConnectionListener(ConnectionListener listener) { |
| | | this.connectionListener = listener; |
| | | } |
| | | |
| | | /** |
| | | * åå§åTCPè¿æ¥ï¼å¼æ¥ï¼ |
| | | */ |
| | | public void connect() { |
| | | if (serverIp == null || serverIp.isEmpty()) { |
| | | Log.e(TAG, "Server IP not set"); |
| | | if (connectionListener != null) { |
| | | connectionListener.onError(new IllegalArgumentException("Server IP not set")); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | if (workerGroup != null) { |
| | | Log.w(TAG, "TCP client already initialized, disconnecting first"); |
| | | disconnect(); |
| | | } |
| | | |
| | | // å建EventLoopGroupï¼ä½¿ç¨å线ç¨ç»å³å¯ï¼ |
| | | workerGroup = new NioEventLoopGroup(1); |
| | | |
| | | try { |
| | | Bootstrap bootstrap = new Bootstrap(); |
| | | bootstrap.group(workerGroup) |
| | | .channel(NioSocketChannel.class) |
| | | .option(ChannelOption.TCP_NODELAY, true) // ç¦ç¨Nagleç®æ³ï¼éä½å»¶è¿ |
| | | .option(ChannelOption.SO_KEEPALIVE, true) // å¯ç¨TCP keepalive |
| | | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // è¿æ¥è¶
æ¶5ç§ |
| | | .handler(new ChannelInitializer<SocketChannel>() { |
| | | @Override |
| | | protected void initChannel(SocketChannel ch) throws Exception { |
| | | ch.pipeline().addLast(new TcpClientHandler()); |
| | | } |
| | | }); |
| | | |
| | | Log.d(TAG, "Connecting to TCP server: " + serverIp + ":" + serverPort); |
| | | |
| | | // 弿¥è¿æ¥ |
| | | ChannelFuture future = bootstrap.connect(serverIp, serverPort); |
| | | future.addListener(new ChannelFutureListener() { |
| | | @Override |
| | | public void operationComplete(ChannelFuture future) throws Exception { |
| | | if (future.isSuccess()) { |
| | | channel = future.channel(); |
| | | isConnected = true; |
| | | Log.d(TAG, "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); |
| | | if (connectionListener != null) { |
| | | connectionListener.onError(cause); |
| | | } |
| | | // è¿æ¥å¤±è´¥æ¶æ¸
çèµæº |
| | | shutdown(); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Initialize TCP client failed", e); |
| | | isConnected = false; |
| | | if (connectionListener != null) { |
| | | connectionListener.onError(e); |
| | | } |
| | | shutdown(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * åéRTPå
ï¼TCPæ¹å¼ï¼ |
| | | */ |
| | | public void sendPacket(byte[] packet) { |
| | | if (!isConnected || channel == null || !channel.isActive()) { |
| | | Log.w(TAG, "TCP channel not connected, packet dropped"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // å°åèæ°ç»å
è£
为ByteBuf |
| | | ByteBuf buffer = Unpooled.wrappedBuffer(packet); |
| | | String str = BytesUtils.bytesToHexString( BytesUtils.subArray(buffer.array(),0,30)); |
| | | Log.i(TAG, "Send TCP packet:"+ str); |
| | | // 弿¥åå
¥ |
| | | ChannelFuture future = channel.writeAndFlush(buffer); |
| | | |
| | | // å¯éï¼çå¬åéç»æ |
| | | future.addListener(new ChannelFutureListener() { |
| | | @Override |
| | | public void operationComplete(ChannelFuture future) throws Exception { |
| | | if (!future.isSuccess()) { |
| | | Log.e(TAG, "Send TCP packet failed", future.cause()); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Send TCP packet error", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æå¼TCPè¿æ¥ |
| | | */ |
| | | public void disconnect() { |
| | | isConnected = false; |
| | | |
| | | if (channel != null) { |
| | | try { |
| | | ChannelFuture future = channel.close(); |
| | | future.await(2, TimeUnit.SECONDS); |
| | | } catch (InterruptedException e) { |
| | | Log.w(TAG, "Close channel interrupted", e); |
| | | Thread.currentThread().interrupt(); |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Close channel error", e); |
| | | } |
| | | channel = null; |
| | | } |
| | | |
| | | shutdown(); |
| | | |
| | | if (connectionListener != null) { |
| | | connectionListener.onDisconnected(); |
| | | } |
| | | |
| | | Log.d(TAG, "TCP connection closed"); |
| | | } |
| | | |
| | | /** |
| | | * å
³éEventLoopGroupï¼æ¸
çèµæºï¼ |
| | | */ |
| | | private void shutdown() { |
| | | if (workerGroup != null) { |
| | | try { |
| | | workerGroup.shutdownGracefully().await(3, TimeUnit.SECONDS); |
| | | } catch (InterruptedException e) { |
| | | Log.w(TAG, "Shutdown worker group interrupted", e); |
| | | Thread.currentThread().interrupt(); |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Shutdown worker group error", e); |
| | | } |
| | | workerGroup = null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ£æ¥æ¯å¦å·²è¿æ¥ |
| | | */ |
| | | public boolean isConnected() { |
| | | return isConnected && channel != null && channel.isActive(); |
| | | } |
| | | |
| | | /** |
| | | * TCP客æ·ç«¯ééå¤çå¨ |
| | | */ |
| | | private class TcpClientHandler extends ChannelInboundHandlerAdapter { |
| | | |
| | | @Override |
| | | public void channelActive(ChannelHandlerContext ctx) throws Exception { |
| | | super.channelActive(ctx); |
| | | Log.d(TAG, "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()); |
| | | isConnected = false; |
| | | channel = null; |
| | | if (connectionListener != null) { |
| | | connectionListener.onDisconnected(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
| | | Log.e(TAG, "TCP channel exception", cause); |
| | | isConnected = false; |
| | | if (connectionListener != null) { |
| | | connectionListener.onError(cause); |
| | | } |
| | | ctx.close(); |
| | | } |
| | | |
| | | @Override |
| | | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { |
| | | // 妿æå¡å¨æååºï¼å¯ä»¥å¨è¿éå¤ç |
| | | // 对äºJT/T 1076-2016 RTPåéï¼é常ä¸éè¦å¤çååº |
| | | ByteBuf buf = (ByteBuf) msg; |
| | | Log.d(TAG, "Received data from server: " + buf.readableBytes() + " bytes"); |
| | | buf.release(); // éæ¾ByteBuf |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | h264Encoder?.setEnableFileOutput(true) // å¯ç¨æä»¶è¾åº |
| | | |
| | | // 设置UDPæå¡å¨å°åï¼å¯éï¼ |
| | | h264Encoder?.setServerAddress("192.168.1.100", 8888) |
| | | h264Encoder?.setProtocolParams("123456789012", 1) |
| | | // h264Encoder?.setServerAddress("58.48.93.67", 11935) |
| | | h264Encoder?.setEnableNetworkTransmission(false) |
| | | h264Encoder?.setServerAddress("192.168.16.12", 11935) |
| | | h264Encoder?.setProtocolParams("013120122580", 1) |
| | | |
| | | // åå§åå¹¶å¯å¨ |
| | | val cameraIdRange = intArrayOf(1, 2) |
| New file |
| | |
| | | package com.anyun.h264.model; |
| | | |
| | | import android.os.Parcel; |
| | | import android.os.Parcelable; |
| | | |
| | | /** |
| | | * é³è§é¢èµæºä¿¡æ¯ï¼æ ¹æ®JT/T 1076-2016表23å®ä¹ï¼ |
| | | * ç»ç«¯ä¸ä¼ é³è§é¢èµæºåè¡¨æ ¼å¼ |
| | | */ |
| | | public class ResourceInfo implements Parcelable { |
| | | /** é»è¾ééå·ï¼æ ¹æ®JT/T 1076â2016表2ï¼ */ |
| | | private byte logicalChannelNumber; |
| | | |
| | | /** å¼å§æ¶é´ï¼BCD[6]æ ¼å¼ï¼YY-MM-DD-HH-MM-SSï¼ */ |
| | | private String startTime; // BCDç¼ç ç6åèï¼æ ¼å¼ï¼YYMMDDHHmmss |
| | | |
| | | /** ç»ææ¶é´ï¼BCD[6]æ ¼å¼ï¼YY-MM-DD-HH-MM-SSï¼ */ |
| | | private String endTime; // BCDç¼ç ç6åèï¼æ ¼å¼ï¼YYMMDDHHmmss |
| | | |
| | | /** æ¥è¦æ å¿ï¼64ä½ï¼ */ |
| | | private long alarmFlag; |
| | | |
| | | /** é³è§é¢èµæºç±»åï¼0-é³è§é¢ï¼1-é³é¢ï¼2-è§é¢ */ |
| | | private byte resourceType; |
| | | |
| | | /** ç æµç±»åï¼1-ä¸»ç æµï¼2-åç æµ */ |
| | | private byte streamType; |
| | | |
| | | /** åå¨å¨ç±»åï¼1-主åå¨å¨ï¼2-ç¾å¤åå¨å¨ */ |
| | | private byte storageType; |
| | | |
| | | /** æä»¶å¤§å°ï¼åä½ï¼åèï¼ */ |
| | | private long fileSize; |
| | | |
| | | public ResourceInfo() { |
| | | } |
| | | |
| | | protected ResourceInfo(Parcel in) { |
| | | logicalChannelNumber = in.readByte(); |
| | | startTime = in.readString(); |
| | | endTime = in.readString(); |
| | | alarmFlag = in.readLong(); |
| | | resourceType = in.readByte(); |
| | | streamType = in.readByte(); |
| | | storageType = in.readByte(); |
| | | fileSize = in.readLong(); |
| | | } |
| | | |
| | | public static final Creator<ResourceInfo> CREATOR = new Creator<ResourceInfo>() { |
| | | @Override |
| | | public ResourceInfo createFromParcel(Parcel in) { |
| | | return new ResourceInfo(in); |
| | | } |
| | | |
| | | @Override |
| | | public ResourceInfo[] newArray(int size) { |
| | | return new ResourceInfo[size]; |
| | | } |
| | | }; |
| | | |
| | | @Override |
| | | public void writeToParcel(Parcel dest, int flags) { |
| | | dest.writeByte(logicalChannelNumber); |
| | | dest.writeString(startTime); |
| | | dest.writeString(endTime); |
| | | dest.writeLong(alarmFlag); |
| | | dest.writeByte(resourceType); |
| | | dest.writeByte(streamType); |
| | | dest.writeByte(storageType); |
| | | dest.writeLong(fileSize); |
| | | } |
| | | |
| | | @Override |
| | | public int describeContents() { |
| | | return 0; |
| | | } |
| | | |
| | | // Getters and Setters |
| | | public byte getLogicalChannelNumber() { |
| | | return logicalChannelNumber; |
| | | } |
| | | |
| | | public void setLogicalChannelNumber(byte logicalChannelNumber) { |
| | | this.logicalChannelNumber = logicalChannelNumber; |
| | | } |
| | | |
| | | public String getStartTime() { |
| | | return startTime; |
| | | } |
| | | |
| | | public void setStartTime(String startTime) { |
| | | this.startTime = startTime; |
| | | } |
| | | |
| | | public String getEndTime() { |
| | | return endTime; |
| | | } |
| | | |
| | | public void setEndTime(String endTime) { |
| | | this.endTime = endTime; |
| | | } |
| | | |
| | | public long getAlarmFlag() { |
| | | return alarmFlag; |
| | | } |
| | | |
| | | public void setAlarmFlag(long alarmFlag) { |
| | | this.alarmFlag = alarmFlag; |
| | | } |
| | | |
| | | public byte getResourceType() { |
| | | return resourceType; |
| | | } |
| | | |
| | | public void setResourceType(byte resourceType) { |
| | | this.resourceType = resourceType; |
| | | } |
| | | |
| | | public byte getStreamType() { |
| | | return streamType; |
| | | } |
| | | |
| | | public void setStreamType(byte streamType) { |
| | | this.streamType = streamType; |
| | | } |
| | | |
| | | public byte getStorageType() { |
| | | return storageType; |
| | | } |
| | | |
| | | public void setStorageType(byte storageType) { |
| | | this.storageType = storageType; |
| | | } |
| | | |
| | | public long getFileSize() { |
| | | return fileSize; |
| | | } |
| | | |
| | | public void setFileSize(long fileSize) { |
| | | this.fileSize = fileSize; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "ResourceInfo{" + |
| | | "logicalChannelNumber=" + logicalChannelNumber + |
| | | ", startTime='" + startTime + '\'' + |
| | | ", endTime='" + endTime + '\'' + |
| | | ", alarmFlag=" + alarmFlag + |
| | | ", resourceType=" + resourceType + |
| | | ", streamType=" + streamType + |
| | | ", storageType=" + storageType + |
| | | ", fileSize=" + fileSize + |
| | | '}'; |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | package com.anyun.h264.service; |
| | | |
| | | import android.content.ComponentName; |
| | | import android.content.Context; |
| | | import android.content.Intent; |
| | | import android.content.ServiceConnection; |
| | | import android.os.IBinder; |
| | | import android.os.RemoteException; |
| | | import android.util.Log; |
| | | |
| | | import com.anyun.h264.IH264EncodeService; |
| | | import com.anyun.h264.model.ResourceInfo; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * H264ç¼ç æå¡å®¢æ·ç«¯ |
| | | * ç¨äºç»å®æå¡å¹¶è°ç¨AIDLæ¥å£ |
| | | * |
| | | * 使ç¨ç¤ºä¾ï¼ |
| | | * <pre> |
| | | * // å建客æ·ç«¯ |
| | | * H264EncodeServiceClient client = new H264EncodeServiceClient(context); |
| | | * |
| | | * // ç»å®æå¡ |
| | | * client.bindService(); |
| | | * |
| | | * // çå¾
æå¡ç»å®å®æåï¼è°ç¨æ¥å£ |
| | | * // å¼å¯æä»¶ç¼ç ï¼å¸¦é
ç½®åæ°ï¼ |
| | | * String jsonConfig = "{\"width\":640,\"height\":480,\"framerate\":25}"; |
| | | * int result = client.controlEncode(0, jsonConfig); |
| | | * |
| | | * // å¼å¯ç½ç»æ¨éï¼å¸¦é
ç½®åæ°ï¼ |
| | | * String networkConfig = "{\"ip\":\"192.168.1.100\",\"port\":8888,\"width\":1280,\"height\":720,\"framerate\":30}"; |
| | | * result = client.controlEncode(2, networkConfig); |
| | | * |
| | | * // 忢ç¼ç ï¼ä¸éè¦é
ç½®åæ°ï¼ |
| | | * client.controlEncode(1, null); |
| | | * |
| | | * // è·åèµæºå表 |
| | | * List<ResourceInfo> resources = client.getResourceList("240101000000", "240101235959"); |
| | | * |
| | | * // è§£ç»æå¡ |
| | | * client.unbindService(); |
| | | * </pre> |
| | | */ |
| | | public class H264EncodeServiceClient { |
| | | private static final String TAG = "H264EncodeClient"; |
| | | |
| | | private Context context; |
| | | private IH264EncodeService service; |
| | | private boolean isBound = false; |
| | | |
| | | private ServiceConnection connection = new ServiceConnection() { |
| | | @Override |
| | | public void onServiceConnected(ComponentName name, IBinder binder) { |
| | | Log.d(TAG, "Service connected"); |
| | | service = IH264EncodeService.Stub.asInterface(binder); |
| | | isBound = true; |
| | | |
| | | // å¯ä»¥å¨è¿éæ·»å æå¡è¿æ¥æåçåè° |
| | | if (onServiceConnectedListener != null) { |
| | | onServiceConnectedListener.onConnected(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onServiceDisconnected(ComponentName name) { |
| | | Log.d(TAG, "Service disconnected"); |
| | | service = null; |
| | | isBound = false; |
| | | |
| | | // å¯ä»¥å¨è¿éæ·»å æå¡æå¼è¿æ¥çåè° |
| | | if (onServiceDisconnectedListener != null) { |
| | | onServiceDisconnectedListener.onDisconnected(); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // æå¡è¿æ¥åè°æ¥å£ |
| | | public interface OnServiceConnectedListener { |
| | | void onConnected(); |
| | | } |
| | | |
| | | public interface OnServiceDisconnectedListener { |
| | | void onDisconnected(); |
| | | } |
| | | |
| | | private OnServiceConnectedListener onServiceConnectedListener; |
| | | private OnServiceDisconnectedListener onServiceDisconnectedListener; |
| | | |
| | | public H264EncodeServiceClient(Context context) { |
| | | this.context = context.getApplicationContext(); |
| | | } |
| | | |
| | | /** |
| | | * 设置æå¡è¿æ¥çå¬å¨ |
| | | */ |
| | | public void setOnServiceConnectedListener(OnServiceConnectedListener listener) { |
| | | this.onServiceConnectedListener = listener; |
| | | } |
| | | |
| | | /** |
| | | * 设置æå¡æå¼è¿æ¥çå¬å¨ |
| | | */ |
| | | public void setOnServiceDisconnectedListener(OnServiceDisconnectedListener listener) { |
| | | this.onServiceDisconnectedListener = listener; |
| | | } |
| | | |
| | | /** |
| | | * ç»å®æå¡ |
| | | * @return æ¯å¦æåå¯å¨ç»å® |
| | | */ |
| | | public boolean bindService() { |
| | | if (isBound) { |
| | | Log.w(TAG, "Service is already bound"); |
| | | return true; |
| | | } |
| | | |
| | | Intent intent = new Intent(); |
| | | intent.setComponent(new ComponentName(context, "com.anyun.h264.H264EncodeService")); |
| | | |
| | | boolean result = context.bindService(intent, connection, Context.BIND_AUTO_CREATE); |
| | | if (!result) { |
| | | Log.e(TAG, "Failed to bind service"); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * è§£ç»æå¡ |
| | | */ |
| | | public void unbindService() { |
| | | if (isBound) { |
| | | context.unbindService(connection); |
| | | isBound = false; |
| | | service = null; |
| | | Log.d(TAG, "Service unbound"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ£æ¥æå¡æ¯å¦å·²ç»å® |
| | | */ |
| | | public boolean isServiceBound() { |
| | | return isBound && service != null; |
| | | } |
| | | |
| | | /** |
| | | * æ§å¶H264ç¼ç |
| | | * @param action æä½ç±»åï¼0-å¼å¯h264æä»¶åå
¥ï¼1-忢h264ç¼ç 并忢åå
¥æä»¶ï¼2-å¼å¯ç½ç»æ¨éh264ï¼ä¸åå
¥æä»¶ï¼ï¼3-忢h264ç¼ç 并忢ç½ç»æ¨é |
| | | * @param jsonConfig JSONæ ¼å¼çé
ç½®åæ°ï¼å
å«ï¼ipï¼æå¡å¨IPï¼ãportï¼æå¡å¨ç«¯å£ï¼ãwidthï¼è§é¢å®½åº¦ï¼ãheightï¼è§é¢é«åº¦ï¼ãframerateï¼å¸§çï¼ãsimPhoneï¼SIMå¡å·ï¼ |
| | | * 示ä¾ï¼{"ip":"192.168.1.100","port":8888,"width":640,"height":480,"framerate":25,"simPhone":"013120122580"} |
| | | * 妿action为1æ3ï¼åæ¢æä½ï¼ï¼æ¤åæ°å¯ä¸ºnull |
| | | * @return 0-æåï¼1-失败 |
| | | */ |
| | | public int controlEncode(int action, String jsonConfig) { |
| | | if (!isServiceBound()) { |
| | | Log.e(TAG, "Service is not bound"); |
| | | return 1; // 失败 |
| | | } |
| | | |
| | | try { |
| | | int result = service.controlEncode(action, jsonConfig); |
| | | Log.d(TAG, "controlEncode(" + action + ", " + jsonConfig + ") returned: " + result); |
| | | return result; |
| | | } catch (RemoteException e) { |
| | | Log.e(TAG, "Error calling controlEncode", e); |
| | | return 1; // 失败 |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ§å¶H264ç¼ç ï¼éè½½æ¹æ³ï¼å
¼å®¹æ§ä»£ç ï¼ä½¿ç¨nullä½ä¸ºjsonConfigï¼ |
| | | * @param action æä½ç±»åï¼0-å¼å¯h264æä»¶åå
¥ï¼1-忢h264ç¼ç 并忢åå
¥æä»¶ï¼2-å¼å¯ç½ç»æ¨éh264ï¼ä¸åå
¥æä»¶ï¼ï¼3-忢h264ç¼ç 并忢ç½ç»æ¨é |
| | | * @return 0-æåï¼1-失败 |
| | | * @deprecated å»ºè®®ä½¿ç¨ controlEncode(int action, String jsonConfig) æ¹æ³ï¼ä¼ å
¥å®æ´çé
ç½®åæ° |
| | | */ |
| | | @Deprecated |
| | | public int controlEncode(int action) { |
| | | return controlEncode(action, null); |
| | | } |
| | | |
| | | /** |
| | | * è·åèµæºå表 |
| | | * @param startTime å¼å§æ¶é´ï¼æ ¼å¼ï¼YYMMDDHHmmssï¼ä¾å¦ï¼240101000000ï¼ |
| | | * @param endTime ç»ææ¶é´ï¼æ ¼å¼ï¼YYMMDDHHmmssï¼ä¾å¦ï¼240101235959ï¼ |
| | | * @return èµæºå表ï¼å¦æå¤±è´¥è¿ånull |
| | | */ |
| | | public List<ResourceInfo> getResourceList(String startTime, String endTime) { |
| | | if (!isServiceBound()) { |
| | | Log.e(TAG, "Service is not bound"); |
| | | return null; |
| | | } |
| | | |
| | | try { |
| | | List<ResourceInfo> result = service.getResourceList(startTime, endTime); |
| | | Log.d(TAG, "getResourceList returned " + (result != null ? result.size() : 0) + " resources"); |
| | | return result; |
| | | } catch (RemoteException e) { |
| | | Log.e(TAG, "Error calling getResourceList", e); |
| | | return null; |
| | | } |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | package com.anyun.h264.util; |
| | | |
| | | import java.lang.reflect.Array; |
| | | import java.nio.ByteBuffer; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.stream.Stream; |
| | | |
| | | public final class BytesUtils { |
| | | public static String bytesToHexString(byte[] src,int len){ |
| | | return bytesToHexString(src,0,len); |
| | | } |
| | | public static String bytesToHexString(byte[] src){ |
| | | return bytesToHexString(src,0,src.length); |
| | | } |
| | | public static String byteToHexString(byte b) |
| | | { |
| | | int v = b & 0xFF; |
| | | return Integer.toHexString(v); |
| | | } |
| | | |
| | | public static float bytesToFloat(byte[] src, int start, int len) { |
| | | ByteBuffer buffer = ByteBuffer.wrap(src, start, len); |
| | | return buffer.getFloat(); |
| | | } |
| | | |
| | | public static String bytesToHexString(byte[] src,int start,int len){ |
| | | StringBuilder stringBuilder = new StringBuilder(""); |
| | | if (src == null || src.length <= 0) { |
| | | return null; |
| | | } |
| | | for (int i = start; i < start + len; i++) { |
| | | int v = src[i] & 0xFF; |
| | | String hv = Integer.toHexString(v); |
| | | if (hv.length() < 2) { |
| | | stringBuilder.append(0); |
| | | } |
| | | stringBuilder.append(hv); |
| | | } |
| | | return stringBuilder.toString().toUpperCase(); |
| | | } |
| | | public static byte[] hexStringToBytes(String hexString) { |
| | | if (hexString == null || hexString.equals("")) { |
| | | return null; |
| | | } |
| | | hexString = hexString.toUpperCase(); |
| | | int length = hexString.length() / 2; |
| | | char[] hexChars = hexString.toCharArray(); |
| | | byte[] d = new byte[length]; |
| | | for (int i = 0; i < length; i++) { |
| | | int pos = i * 2; |
| | | d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); |
| | | } |
| | | return d; |
| | | } |
| | | private static byte charToByte(char c) { |
| | | return (byte) "0123456789ABCDEF".indexOf(c); |
| | | } |
| | | |
| | | public static byte xor(byte[] bytes,int start,int size) |
| | | { |
| | | if (bytes.length < size) |
| | | { |
| | | throw new RuntimeException("计ç®xorçæ¶åsizeä¸è¶³"); |
| | | } |
| | | int s1 = bytes[start]; |
| | | for (int i = start + 1; i < size; i++) |
| | | { |
| | | s1 = s1 ^ bytes[i]; |
| | | } |
| | | return (byte)s1; |
| | | } |
| | | |
| | | public static int ccitt(byte[] data, int start, int size) { |
| | | final int []table = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, |
| | | 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, |
| | | 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, |
| | | 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, |
| | | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, |
| | | 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, |
| | | 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, |
| | | 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, |
| | | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, |
| | | 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, |
| | | 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, |
| | | 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, |
| | | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, |
| | | 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, |
| | | 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, |
| | | 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, |
| | | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, |
| | | 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, |
| | | 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, |
| | | 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, |
| | | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, |
| | | 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, |
| | | 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, |
| | | 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, |
| | | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, |
| | | 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, |
| | | 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, |
| | | 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, |
| | | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, |
| | | 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, |
| | | 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, |
| | | 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, |
| | | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, |
| | | 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, |
| | | 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, |
| | | 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, |
| | | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, |
| | | 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, |
| | | 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, |
| | | 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, |
| | | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, |
| | | 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, |
| | | 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0}; |
| | | int crc = 0; |
| | | int finall = 0; |
| | | |
| | | for (int i = start; i < start + size; i++) { |
| | | int temp = (data[i] ^ (crc >> 8)) & 0xff; |
| | | crc = table[temp] ^ (crc << 8); |
| | | } |
| | | |
| | | return crc ^ finall; |
| | | } |
| | | |
| | | public static byte xor(byte[] bytes) |
| | | { |
| | | return xor(bytes,0,bytes.length); |
| | | } |
| | | public static String toHexString(byte b) |
| | | { |
| | | String hex = Integer.toHexString(b&0xff).toUpperCase(); |
| | | if(hex.length()==1) |
| | | { |
| | | return "0"+hex; |
| | | } |
| | | return hex; |
| | | } |
| | | |
| | | public static int Sum(byte[] bytes) |
| | | { |
| | | int sum = 0; |
| | | for(byte b:bytes) |
| | | { |
| | | sum+=b; |
| | | } |
| | | return sum; |
| | | } |
| | | |
| | | public static boolean isAllZero(byte[] bytes) |
| | | { |
| | | for (byte b:bytes) { |
| | | if (b != 0) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | public static int getInt32(byte[] bytes,int start){ |
| | | int result = 0; |
| | | int a = (bytes[0+start] & 0xff) << 24; |
| | | int b = (bytes[1+start] & 0xff) << 16; |
| | | int c = (bytes[2+start] & 0xff) << 8; |
| | | int d = (bytes[3+start] & 0xff); |
| | | result = a | b | c | d; |
| | | return result; |
| | | } |
| | | |
| | | public static byte[] subArray(byte[] array,int start,int len) |
| | | { |
| | | byte[] result = new byte[len]; |
| | | System.arraycopy(array,start,result,0,len); |
| | | return result; |
| | | } |
| | | |
| | | private static int findPos(byte[] array,byte find,int start) |
| | | { |
| | | for(int i=start;i<array.length;i++) |
| | | { |
| | | if(array[i]==find) return i; |
| | | } |
| | | return -1; |
| | | } |
| | | |
| | | public static List<byte[]> splitArray(byte[] bytes, byte spliter) |
| | | { |
| | | ArrayList<byte[]> list = new ArrayList(); |
| | | int start = 0; |
| | | while (true) { |
| | | int pos = findPos(bytes, spliter,start); |
| | | if(pos>=0) |
| | | { |
| | | byte[] findbytes = subArray(bytes,start,pos - start); |
| | | list.add(findbytes); |
| | | start = pos + 1; |
| | | } |
| | | else |
| | | { |
| | | break; |
| | | } |
| | | } |
| | | //æ·»å æåä¸ä¸ªæ°ç» |
| | | byte[] findbytes = subArray(bytes,start,bytes.length - start); |
| | | list.add(findbytes); |
| | | return list; |
| | | } |
| | | } |
| New file |
| | |
| | | #!/usr/bin/env python3 |
| | | # -*- coding: utf-8 -*- |
| | | """ |
| | | H264æä»¶æ£æ¥å·¥å
· |
| | | æ£æ¥çæçtest.h264æä»¶æ¯å¦ç¬¦åææ¾è¦æ± |
| | | """ |
| | | |
| | | import os |
| | | import sys |
| | | |
| | | def check_h264_file(file_path): |
| | | """æ£æ¥H264æä»¶æ¯å¦ç¬¦åææ¾è¦æ±""" |
| | | print(f"æ£æ¥æä»¶: {file_path}\n") |
| | | |
| | | # 1. æ£æ¥æä»¶æ¯å¦åå¨ |
| | | if not os.path.exists(file_path): |
| | | print("â æä»¶ä¸åå¨ï¼") |
| | | print(f" é¢æè·¯å¾: {file_path}") |
| | | print("\næç¤ºï¼") |
| | | print(" - 妿æ¯Android设å¤ï¼éè¦å
éè¿adb pullä¸è½½æä»¶") |
| | | print(f" - æè
å°æä»¶å¤å¶å°å½åç®å½") |
| | | return False |
| | | |
| | | # 2. æ£æ¥æä»¶å¤§å° |
| | | file_size = os.path.getsize(file_path) |
| | | print(f"â æä»¶å¤§å°: {file_size} åè ({file_size / 1024:.2f} KB)") |
| | | |
| | | if file_size == 0: |
| | | print("â æä»¶ä¸ºç©ºï¼") |
| | | return False |
| | | |
| | | if file_size < 100: |
| | | print("â ï¸ è¦å: æä»¶å¤ªå°ï¼å¯è½åªæSPS/PPSæ1å¸§æ°æ®") |
| | | |
| | | # 3. 读åæä»¶å
å®¹å¹¶åæ |
| | | try: |
| | | with open(file_path, 'rb') as f: |
| | | data = f.read() |
| | | except Exception as e: |
| | | print(f"â 读åæä»¶å¤±è´¥: {e}") |
| | | return False |
| | | |
| | | # æ£æ¥NALUåå
|
| | | nal_start_code_3 = bytes([0x00, 0x00, 0x01]) |
| | | nal_start_code_4 = bytes([0x00, 0x00, 0x00, 0x01]) |
| | | |
| | | nalu_list = [] |
| | | i = 0 |
| | | |
| | | # æ¥æ¾ææNALUåå
|
| | | while i < len(data): |
| | | # æ¥æ¾èµ·å§ç |
| | | found = False |
| | | |
| | | # æ¥æ¾4åèèµ·å§ç |
| | | if i + 4 <= len(data) and data[i:i+4] == nal_start_code_4: |
| | | start_pos = i + 4 |
| | | found = True |
| | | # æ¥æ¾3åèèµ·å§ç |
| | | elif i + 3 <= len(data) and data[i:i+3] == nal_start_code_3: |
| | | # ç¡®ä¿åé¢ä¸æ¯0x00 (é¿å
误å¤) |
| | | if i == 0 or data[i-1] != 0x00: |
| | | start_pos = i + 3 |
| | | found = True |
| | | |
| | | if found: |
| | | # æ¥æ¾ä¸ä¸ä¸ªèµ·å§ç |
| | | next_start = -1 |
| | | |
| | | # å
æ¾4åèèµ·å§ç |
| | | for j in range(start_pos, len(data) - 3): |
| | | if j + 4 <= len(data) and data[j:j+4] == nal_start_code_4: |
| | | next_start = j |
| | | break |
| | | |
| | | # å¦ææ²¡æ¾å°4åèçï¼æ¾3åèç |
| | | if next_start == -1: |
| | | for j in range(start_pos, len(data) - 2): |
| | | if j + 3 <= len(data) and data[j:j+3] == nal_start_code_3: |
| | | # ç¡®ä¿åé¢ä¸æ¯0x00 |
| | | if j == 0 or data[j-1] != 0x00: |
| | | next_start = j |
| | | break |
| | | |
| | | # æåNALUæ°æ® |
| | | if next_start == -1: |
| | | nalu_data = data[start_pos:] |
| | | else: |
| | | nalu_data = data[start_pos:next_start] |
| | | |
| | | if len(nalu_data) > 0: |
| | | # è·åNALUç±»å (第ä¸ä¸ªåèçä½5ä½) |
| | | nal_type = nalu_data[0] & 0x1F |
| | | nalu_list.append({ |
| | | 'type': nal_type, |
| | | 'size': len(nalu_data), |
| | | 'name': get_nalu_type_name(nal_type) |
| | | }) |
| | | |
| | | i = next_start if next_start != -1 else len(data) |
| | | else: |
| | | i += 1 |
| | | |
| | | # 4. åæNALUåå
|
| | | print(f"\nâ æ¾å° {len(nalu_list)} 个NALUåå
\n") |
| | | |
| | | if len(nalu_list) == 0: |
| | | print("â æªæ¾å°ä»»ä½NALUåå
ï¼æä»¶æ ¼å¼å¯è½ä¸æ£ç¡®") |
| | | return False |
| | | |
| | | # ç»è®¡ä¸åç±»åçNALU |
| | | nal_type_count = {} |
| | | for nalu in nalu_list: |
| | | nal_type = nalu['type'] |
| | | if nal_type not in nal_type_count: |
| | | nal_type_count[nal_type] = 0 |
| | | nal_type_count[nal_type] += 1 |
| | | |
| | | print("NALUç±»åç»è®¡:") |
| | | for nal_type in sorted(nal_type_count.keys()): |
| | | count = nal_type_count[nal_type] |
| | | name = get_nalu_type_name(nal_type) |
| | | print(f" {name} (ç±»å{nal_type}): {count} 个") |
| | | |
| | | # 5. æ£æ¥å
³é®è¦æ± |
| | | print("\næ£æ¥é¡¹:") |
| | | |
| | | has_sps = 7 in nal_type_count |
| | | has_pps = 8 in nal_type_count |
| | | has_idr = 5 in nal_type_count |
| | | has_non_idr = 1 in nal_type_count |
| | | |
| | | # SPS/PPSæ£æ¥ |
| | | if has_sps: |
| | | print(f" â å
å«SPS (åºååæ°é) - {nal_type_count[7]} 个") |
| | | else: |
| | | print(" â 缺å°SPS (åºååæ°é) - å¿
éï¼") |
| | | |
| | | if has_pps: |
| | | print(f" â å
å«PPS (å¾ååæ°é) - {nal_type_count[8]} 个") |
| | | else: |
| | | print(" â 缺å°PPS (å¾ååæ°é) - å¿
éï¼") |
| | | |
| | | # å
³é®å¸§æ£æ¥ |
| | | if has_idr: |
| | | print(f" â å
å«IDRå
³é®å¸§ - {nal_type_count[5]} 个") |
| | | else: |
| | | print(" â 缺å°IDRå
³é®å¸§") |
| | | |
| | | # éå
³é®å¸§æ£æ¥ |
| | | if has_non_idr: |
| | | print(f" â å
å«éIDR帧 - {nal_type_count[1]} 个") |
| | | else: |
| | | print(" â ï¸ æ²¡æéIDR帧ï¼åªæå
³é®å¸§ï¼") |
| | | |
| | | # 6. æ»ä½è¯ä¼° |
| | | print("\n" + "="*50) |
| | | can_play = has_sps and has_pps and has_idr |
| | | |
| | | if can_play: |
| | | if len(nalu_list) >= 3: |
| | | print("â
æä»¶åºè¯¥å¯ä»¥ææ¾ï¼") |
| | | print(f" å
å«å®æ´çSPS/PPSå {len(nalu_list)} 个NALUåå
") |
| | | else: |
| | | print("â ï¸ æä»¶ç»æå®æ´ï¼ä½å¸§æ°è¾å°") |
| | | print(" 建议å½å¶æ´é¿æ¶é´ä»¥è·å¾æ´å¤å¸§") |
| | | else: |
| | | print("â æä»¶å¯è½æ æ³ææ¾") |
| | | if not has_sps or not has_pps: |
| | | print(" åå : 缺å°SPS/PPSåæ°é") |
| | | if not has_idr: |
| | | print(" åå : 缺å°IDRå
³é®å¸§") |
| | | |
| | | print("="*50) |
| | | |
| | | return can_play |
| | | |
| | | def get_nalu_type_name(nal_type): |
| | | """è·åNALUç±»ååç§°""" |
| | | nal_names = { |
| | | 1: "éIDRç¼ç ç", |
| | | 2: "ç¼ç çæ°æ®ååºA", |
| | | 3: "ç¼ç çæ°æ®ååºB", |
| | | 4: "ç¼ç çæ°æ®ååºC", |
| | | 5: "IDRå¾åç¼ç ç", |
| | | 6: "SEI (è¡¥å
å¢å¼ºä¿¡æ¯)", |
| | | 7: "SPS (åºååæ°é)", |
| | | 8: "PPS (å¾ååæ°é)", |
| | | 9: "访é®åå
åé符", |
| | | 10: "åºåç»æ", |
| | | 11: "æµç»æ", |
| | | 12: "å¡«å
æ°æ®" |
| | | } |
| | | return nal_names.get(nal_type, f"æªç¥ç±»å{nal_type}") |
| | | |
| | | if __name__ == "__main__": |
| | | # é»è®¤æä»¶è·¯å¾ |
| | | default_path = "test.h264" |
| | | |
| | | # 妿æä¾äºå½ä»¤è¡åæ°ï¼ä½¿ç¨åæ°ä½ä¸ºæä»¶è·¯å¾ |
| | | file_path = sys.argv[1] if len(sys.argv) > 1 else default_path |
| | | |
| | | print("H264æä»¶æ£æ¥å·¥å
·") |
| | | print("="*50) |
| | | |
| | | success = check_h264_file(file_path) |
| | | |
| | | sys.exit(0 if success else 1) |
| | | |
| | |
| | | lifecycleRuntimeKtx = "2.6.1" |
| | | activityCompose = "1.8.0" |
| | | composeBom = "2024.04.01" |
| | | netty = "4.1.48.Final" |
| | | |
| | | [libraries] |
| | | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } |
| | |
| | | androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } |
| | | 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" } |
| | | |
| | | [plugins] |
| | | android-application = { id = "com.android.application", version.ref = "agp" } |
| New file |
| | |
| | | #!/usr/bin/env sh |
| | | |
| | | # |
| | | # Copyright 2015 the original author or authors. |
| | | # |
| | | # Licensed under the Apache License, Version 2.0 (the "License"); |
| | | # you may not use this file except in compliance with the License. |
| | | # You may obtain a copy of the License at |
| | | # |
| | | # https://www.apache.org/licenses/LICENSE-2.0 |
| | | # |
| | | # Unless required by applicable law or agreed to in writing, software |
| | | # distributed under the License is distributed on an "AS IS" BASIS, |
| | | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | # See the License for the specific language governing permissions and |
| | | # limitations under the License. |
| | | # |
| | | |
| | | ############################################################################## |
| | | ## |
| | | ## Gradle start up script for UN*X |
| | | ## |
| | | ############################################################################## |
| | | |
| | | # Attempt to set APP_HOME |
| | | # Resolve links: $0 may be a link |
| | | PRG="$0" |
| | | # Need this for relative symlinks. |
| | | while [ -h "$PRG" ] ; do |
| | | ls=`ls -ld "$PRG"` |
| | | link=`expr "$ls" : '.*-> \(.*\)$'` |
| | | if expr "$link" : '/.*' > /dev/null; then |
| | | PRG="$link" |
| | | else |
| | | PRG=`dirname "$PRG"`"/$link" |
| | | fi |
| | | done |
| | | SAVED="`pwd`" |
| | | cd "`dirname \"$PRG\"`/" >/dev/null |
| | | APP_HOME="`pwd -P`" |
| | | cd "$SAVED" >/dev/null |
| | | |
| | | APP_NAME="Gradle" |
| | | APP_BASE_NAME=`basename "$0"` |
| | | |
| | | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
| | | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' |
| | | |
| | | # Use the maximum available, or set MAX_FD != -1 to use that value. |
| | | MAX_FD="maximum" |
| | | |
| | | warn () { |
| | | echo "$*" |
| | | } |
| | | |
| | | die () { |
| | | echo |
| | | echo "$*" |
| | | echo |
| | | exit 1 |
| | | } |
| | | |
| | | # OS specific support (must be 'true' or 'false'). |
| | | cygwin=false |
| | | msys=false |
| | | darwin=false |
| | | nonstop=false |
| | | case "`uname`" in |
| | | CYGWIN* ) |
| | | cygwin=true |
| | | ;; |
| | | Darwin* ) |
| | | darwin=true |
| | | ;; |
| | | MINGW* ) |
| | | msys=true |
| | | ;; |
| | | NONSTOP* ) |
| | | nonstop=true |
| | | ;; |
| | | esac |
| | | |
| | | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar |
| | | |
| | | |
| | | # Determine the Java command to use to start the JVM. |
| | | if [ -n "$JAVA_HOME" ] ; then |
| | | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
| | | # IBM's JDK on AIX uses strange locations for the executables |
| | | JAVACMD="$JAVA_HOME/jre/sh/java" |
| | | else |
| | | JAVACMD="$JAVA_HOME/bin/java" |
| | | fi |
| | | if [ ! -x "$JAVACMD" ] ; then |
| | | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |
| | | |
| | | Please set the JAVA_HOME variable in your environment to match the |
| | | location of your Java installation." |
| | | fi |
| | | else |
| | | JAVACMD="java" |
| | | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
| | | |
| | | Please set the JAVA_HOME variable in your environment to match the |
| | | location of your Java installation." |
| | | fi |
| | | |
| | | # Increase the maximum file descriptors if we can. |
| | | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then |
| | | MAX_FD_LIMIT=`ulimit -H -n` |
| | | if [ $? -eq 0 ] ; then |
| | | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
| | | MAX_FD="$MAX_FD_LIMIT" |
| | | fi |
| | | ulimit -n $MAX_FD |
| | | if [ $? -ne 0 ] ; then |
| | | warn "Could not set maximum file descriptor limit: $MAX_FD" |
| | | fi |
| | | else |
| | | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
| | | fi |
| | | fi |
| | | |
| | | # For Darwin, add options to specify how the application appears in the dock |
| | | if $darwin; then |
| | | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
| | | fi |
| | | |
| | | # For Cygwin or MSYS, switch paths to Windows format before running java |
| | | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then |
| | | APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
| | | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
| | | |
| | | JAVACMD=`cygpath --unix "$JAVACMD"` |
| | | |
| | | # We build the pattern for arguments to be converted via cygpath |
| | | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
| | | SEP="" |
| | | for dir in $ROOTDIRSRAW ; do |
| | | ROOTDIRS="$ROOTDIRS$SEP$dir" |
| | | SEP="|" |
| | | done |
| | | OURCYGPATTERN="(^($ROOTDIRS))" |
| | | # Add a user-defined pattern to the cygpath arguments |
| | | if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
| | | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
| | | fi |
| | | # Now convert the arguments - kludge to limit ourselves to /bin/sh |
| | | i=0 |
| | | for arg in "$@" ; do |
| | | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
| | | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
| | | |
| | | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
| | | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
| | | else |
| | | eval `echo args$i`="\"$arg\"" |
| | | fi |
| | | i=`expr $i + 1` |
| | | done |
| | | case $i in |
| | | 0) set -- ;; |
| | | 1) set -- "$args0" ;; |
| | | 2) set -- "$args0" "$args1" ;; |
| | | 3) set -- "$args0" "$args1" "$args2" ;; |
| | | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
| | | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
| | | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
| | | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
| | | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
| | | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
| | | esac |
| | | fi |
| | | |
| | | # Escape application args |
| | | save () { |
| | | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done |
| | | echo " " |
| | | } |
| | | APP_ARGS=`save "$@"` |
| | | |
| | | # Collect all arguments for the java command, following the shell quoting and substitution rules |
| | | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" |
| | | |
| | | exec "$JAVACMD" "$@" |
| New file |
| | |
| | | @rem |
| | | @rem Copyright 2015 the original author or authors. |
| | | @rem |
| | | @rem Licensed under the Apache License, Version 2.0 (the "License"); |
| | | @rem you may not use this file except in compliance with the License. |
| | | @rem You may obtain a copy of the License at |
| | | @rem |
| | | @rem https://www.apache.org/licenses/LICENSE-2.0 |
| | | @rem |
| | | @rem Unless required by applicable law or agreed to in writing, software |
| | | @rem distributed under the License is distributed on an "AS IS" BASIS, |
| | | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | @rem See the License for the specific language governing permissions and |
| | | @rem limitations under the License. |
| | | @rem |
| | | |
| | | @if "%DEBUG%" == "" @echo off |
| | | @rem ########################################################################## |
| | | @rem |
| | | @rem Gradle startup script for Windows |
| | | @rem |
| | | @rem ########################################################################## |
| | | |
| | | @rem Set local scope for the variables with windows NT shell |
| | | if "%OS%"=="Windows_NT" setlocal |
| | | |
| | | set DIRNAME=%~dp0 |
| | | if "%DIRNAME%" == "" set DIRNAME=. |
| | | set APP_BASE_NAME=%~n0 |
| | | set APP_HOME=%DIRNAME% |
| | | |
| | | @rem Resolve any "." and ".." in APP_HOME to make it shorter. |
| | | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi |
| | | |
| | | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
| | | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" |
| | | |
| | | @rem Find java.exe |
| | | if defined JAVA_HOME goto findJavaFromJavaHome |
| | | |
| | | set JAVA_EXE=java.exe |
| | | %JAVA_EXE% -version >NUL 2>&1 |
| | | if "%ERRORLEVEL%" == "0" goto execute |
| | | |
| | | echo. |
| | | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
| | | echo. |
| | | echo Please set the JAVA_HOME variable in your environment to match the |
| | | echo location of your Java installation. |
| | | |
| | | goto fail |
| | | |
| | | :findJavaFromJavaHome |
| | | set JAVA_HOME=%JAVA_HOME:"=% |
| | | set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
| | | |
| | | if exist "%JAVA_EXE%" goto execute |
| | | |
| | | echo. |
| | | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% |
| | | echo. |
| | | echo Please set the JAVA_HOME variable in your environment to match the |
| | | echo location of your Java installation. |
| | | |
| | | goto fail |
| | | |
| | | :execute |
| | | @rem Setup the command line |
| | | |
| | | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar |
| | | |
| | | |
| | | @rem Execute Gradle |
| | | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* |
| | | |
| | | :end |
| | | @rem End local scope for the variables with windows NT shell |
| | | if "%ERRORLEVEL%"=="0" goto mainEnd |
| | | |
| | | :fail |
| | | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of |
| | | rem the _cmd.exe /c_ return code! |
| | | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 |
| | | exit /b 1 |
| | | |
| | | :mainEnd |
| | | if "%OS%"=="Windows_NT" endlocal |
| | | |
| | | :omega |
| New file |
| | |
| | | private static PrivateKey getKey(String key, char[] password) throws Exception { |
| | | byte[] cabuf = new BASE64Decoder().decodeBuffer(key); |
| | | KeyStore keyStore = KeyStore.getInstance("PKCS12"); |
| | | keyStore.load(new ByteArrayInputStream(cabuf), password); |
| | | Enumeration<String> aliases = keyStore.aliases(); |
| | | if (!aliases.hasMoreElements()) { |
| | | throw new RuntimeException("no alias found"); |
| | | } |
| | | String alias = aliases.nextElement(); |
| | | PrivateKey privateKey = (RSAPrivateCrtKeyImpl) keyStore.getKey(alias, password); |
| | | return privateKey; |
| | | } |
| | | |
| | | public static String sign(String data, PrivateKey key) throws Exception { |
| | | // MessageDigest md = MessageDigest.getInstance("SHA-256"); |
| | | MessageDigest md = MessageDigest.getInstance("SHA-1"); |
| | | md.update(data.getBytes("utf-8")); |
| | | byte[] hash = md.digest(); |
| | | // Cipher cipher = Cipher.getInstance("RSA"); |
| | | Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); |
| | | cipher.init(Cipher.ENCRYPT_MODE, key); |
| | | byte[] encrypted = cipher.doFinal(hash); |
| | | return HexBin.encode(encrypted); |
| | | } |
| | | |
| | | |
| | | public static String sign(String data, String keyString ,String pwd ) throws Exception { |
| | | char[] password = pwd.toCharArray(); |
| | | |
| | | return sign(data,getKey(keyString,password)); |
| | | } |
| | | |
| | | |
| | | public static void main(String[] args) { |
| | | try{ |
| | | String sign = sign("xuanborobot123456nullnullnull","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","27U777"); |
| | | System.out.println(sign); |
| | | }catch (Exception e){ |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| New file |
| | |
| | | storePassword = 123456 |
| | | keyPassword = 123456 |
| | | keyAlias = key0 |
| | | storeFile = ../key/key.jks |
| New file |
| | |
| | | # å¦ä½æ£æ¥test.h264æä»¶è½å¦ææ¾ |
| | | |
| | | ## å¿«éå¤ææ¹æ³ |
| | | |
| | | ### æ¹æ³1: ç´æ¥ç¨ææ¾å¨æµè¯ï¼æç®åï¼ |
| | | |
| | | 1. **ä»Android设å¤ä¸è½½æä»¶** |
| | | ```bash |
| | | adb pull /storage/emulated/0/Android/data/com.anyun.h264/files/test.h264 ./test.h264 |
| | | ``` |
| | | |
| | | 2. **ç¨VLCææ¾å¨æå¼** |
| | | - ä¸è½½VLC: https://www.videolan.org/vlc/ |
| | | - ç´æ¥åå» `test.h264` æä»¶ |
| | | - æè
ææ¾å°VLCçªå£ |
| | | |
| | | 3. **å¦æè½ææ¾** â
|
| | | - 说ææä»¶æ ¼å¼æ£ç¡® |
| | | - å¯ä»¥çå°è§é¢å
容 |
| | | |
| | | 4. **妿ä¸è½ææ¾** â |
| | | - å¯è½ç¼ºå°SPS/PPS |
| | | - å¯è½åªæ1帧 |
| | | - å¯è½æ ¼å¼é误 |
| | | |
| | | ### æ¹æ³2: 使ç¨Pythonå·¥å
·æ£æ¥ï¼è¯¦ç»åæï¼ |
| | | |
| | | 1. **ç¡®ä¿å®è£
äºPython** (Python 3.6+) |
| | | |
| | | 2. **ä¸è½½æä»¶å°æ¬å°** |
| | | ```bash |
| | | adb pull /storage/emulated/0/Android/data/com.anyun.h264/files/test.h264 ./test.h264 |
| | | ``` |
| | | |
| | | 3. **è¿è¡æ£æ¥å·¥å
·** |
| | | ```bash |
| | | python check_h264.py test.h264 |
| | | ``` |
| | | |
| | | 4. **æ¥çæ£æ¥ç»æ** |
| | | |
| | | â
**å¯ä»¥ææ¾çæ å¿ï¼** |
| | | - â å
å«SPS (åºååæ°é) |
| | | - â å
å«PPS (å¾ååæ°é) |
| | | - â å
å«IDRå
³é®å¸§ |
| | | - â æå¤ä¸ªNALUåå
ï¼å»ºè®®>10ä¸ªï¼ |
| | | |
| | | â **ä¸è½ææ¾çæ å¿ï¼** |
| | | - 缺å°SPSæPPS |
| | | - åªæ1-2个NALUåå
|
| | | - æä»¶å¤ªå°ï¼<100åèï¼ |
| | | |
| | | ### æ¹æ³3: 使ç¨ffprobeæ£æ¥ï¼å¦æå®è£
äºFFmpegï¼ |
| | | |
| | | ```bash |
| | | ffprobe test.h264 |
| | | ``` |
| | | |
| | | å¦æè½æ£ç¡®æ¾ç¤ºè§é¢ä¿¡æ¯ï¼å辨çã帧ççï¼ï¼è¯´ææä»¶å¯ä»¥ææ¾ã |
| | | |
| | | ### æ¹æ³4: æ¥çæä»¶å¤§å°ï¼ç²ç¥å¤æï¼ |
| | | |
| | | ```bash |
| | | # å¨Android设å¤ä¸ |
| | | adb shell "ls -lh /storage/emulated/0/Android/data/com.anyun.h264/files/test.h264" |
| | | ``` |
| | | |
| | | **åèæ åï¼** |
| | | - â
**å¯ä»¥ææ¾**ï¼é常 > 10KBï¼640x480@25fpsï¼3ç§çº¦30-50KBï¼ |
| | | - â ï¸ **å¯è½åªæ1帧**ï¼< 5KB |
| | | - â **æä»¶å¼å¸¸**ï¼= 0KB |
| | | |
| | | ## æ£æ¥æä»¶å
容ï¼é«çº§ï¼ |
| | | |
| | | ### 使ç¨hexdumpæ¥çæä»¶å¤´ |
| | | |
| | | ```bash |
| | | hexdump -C test.h264 | head -20 |
| | | ``` |
| | | |
| | | **æ£å¸¸æä»¶åºè¯¥çå°ï¼** |
| | | ``` |
| | | 00000000 00 00 00 01 67 64 00 1f ac 72 84 44 26 84 00 00 |....gd...r.D&...| |
| | | 00000010 00 01 00 00 00 01 68 ee 3c b0 44 00 00 00 01 06 |......h.<.D.....| |
| | | ``` |
| | | |
| | | - `00 00 00 01` = Annex-Bèµ·å§ç ï¼4åèï¼ |
| | | - `67` = SPSçNALUç±»åï¼0x67 & 0x1F = 7ï¼ |
| | | - `68` = PPSçNALUç±»åï¼0x68 & 0x1F = 8ï¼ |
| | | |
| | | ### 使ç¨adbå¨è®¾å¤ä¸ç´æ¥æ£æ¥ |
| | | |
| | | ```bash |
| | | # æ£æ¥æä»¶å¤§å° |
| | | adb shell "stat -c '%s' /storage/emulated/0/Android/data/com.anyun.h264/files/test.h264" |
| | | |
| | | # æ¥çæä»¶å50åè |
| | | adb shell "hexdump -C /storage/emulated/0/Android/data/com.anyun.h264/files/test.h264 | head -5" |
| | | ``` |
| | | |
| | | ## 常è§é®é¢è¯æ |
| | | |
| | | ### â é®é¢1: æä»¶åªæ1帧ã0ç§ |
| | | |
| | | **åå ï¼** |
| | | - ç¼ç å¨è¾åºå¤çä¸å®æ´ |
| | | - SPS/PPSæ²¡ææ£ç¡®åå
¥ |
| | | - ç¼ç å¾ªç¯æåéåº |
| | | |
| | | **è§£å³æ¹æ¡ï¼** |
| | | â
å·²ç»ä¿®å¤ï¼æ°ä»£ç å
å«ï¼ |
| | | - æ£ç¡®å¤çSPS/PPSé
ç½®æ°æ® |
| | | - å¨å
³é®å¸§æ¶åå¹¶SPS/PPS |
| | | - æ¹è¿ç¼ç 循ç¯ï¼å¤çææè¾åº |
| | | - æ·»å æ¸
空ç¼ç å¨åè½ |
| | | |
| | | **建议ï¼** |
| | | 1. éæ°ç¼è¯è¿è¡åºç¨ |
| | | 2. å½å¶è³å°3-5ç§ |
| | | 3. æ£å¸¸åæ¢ç¼ç ï¼ä¸è¦å¼ºå¶éåºï¼ |
| | | |
| | | ### â é®é¢2: VLCæ æ³ææ¾ |
| | | |
| | | **æ£æ¥æ¥éª¤ï¼** |
| | | |
| | | 1. **æ¥çæä»¶å¤§å°** |
| | | ```bash |
| | | adb shell "ls -lh /storage/emulated/0/Android/data/com.anyun.h264/files/test.h264" |
| | | ``` |
| | | å¦æå¤ªå°ï¼<1KBï¼ï¼è¯´æå¯è½åªæé
ç½®æ°æ® |
| | | |
| | | 2. **æ¥çLogcatæ¥å¿** |
| | | ```bash |
| | | adb logcat -s H264Encoder:D | grep -E "Frame encoded|SPS/PPS|NALU" |
| | | ``` |
| | | åºè¯¥çå°ï¼ |
| | | - "SPS/PPS included in key frame data" |
| | | - "Frame encoded: ..." ï¼å¤æ¬¡ï¼ |
| | | |
| | | 3. **æ£æ¥æä»¶æ ¼å¼** |
| | | ```bash |
| | | adb shell "hexdump -C /storage/emulated/0/Android/data/com.anyun.h264/files/test.h264 | head -3" |
| | | ``` |
| | | åºè¯¥çå° `00 00 00 01` æ `00 00 01` |
| | | |
| | | ### â é®é¢3: æä»¶åå¨ä½ææ¾å¨æ¥é |
| | | |
| | | **å¯è½åå ï¼** |
| | | 1. æä»¶æ ¼å¼ä¸æ¯çº¯Annex-B |
| | | 2. 缺å°SPS/PPS |
| | | 3. æ°æ®æå |
| | | |
| | | **è§£å³æ¹æ³ï¼** |
| | | 1. ä½¿ç¨ `check_h264.py` è¯¦ç»æ£æ¥ |
| | | 2. æ¥çLogcat确认ç¼ç è¿ç¨æ£å¸¸ |
| | | 3. å°è¯ç¨ffmpeg转æ¢ï¼ |
| | | ```bash |
| | | ffmpeg -i test.h264 -c copy test_fixed.h264 |
| | | ``` |
| | | |
| | | ## éªè¯ä¿®å¤ææ |
| | | |
| | | ä¿®å¤åç代ç åºè¯¥è½å¤ï¼ |
| | | |
| | | â
**çæå¯ææ¾çH264æä»¶** |
| | | - å
å«å®æ´çSPS/PPS |
| | | - å
å«å¤ä¸ªå¸§ï¼IDR + éIDRï¼ |
| | | - æ£ç¡®çAnnex-Bæ ¼å¼ |
| | | |
| | | â
**æä»¶ç¹å¾ï¼** |
| | | - æä»¶å¤§å° > 10KBï¼3ç§å½å¶ï¼ |
| | | - NALUåå
æ°é > 10个 |
| | | - å
å«SPS (ç±»å7) |
| | | - å
å«PPS (ç±»å8) |
| | | - å
å«IDRå
³é®å¸§ (ç±»å5) |
| | | - å
å«éIDR帧 (ç±»å1) |
| | | |
| | | ## ä¸ä¸æ¥ |
| | | |
| | | 1. **éæ°è¿è¡åºç¨**ï¼å½å¶3-5ç§è§é¢ |
| | | 2. **ä¸è½½æä»¶**å°æ¬å° |
| | | 3. **ç¨VLCææ¾**éªè¯ |
| | | 4. **å¦æè¿æé®é¢**ï¼è¿è¡æ£æ¥å·¥å
·è·å详ç»è¯æ |
| | | |