2.功能接口说明

2.1 宏定义声明以及注释

SDK 中的所有字段定义都可以在 JLDV16SDK.h 中找到,具体使用命令时的回调通知,也是根据这些字段的定义来接收;

另外,3.附录 中分别有关于命令的返回出错内容和其他文件的一些格式。

  1#pragma mark <- 错误列表 ->
  2//错误列表
  3#define ENONE               0      // 无错误
  4#define E_SDCARD            1      // SD卡错误
  5#define E_SD_OFFLINE        2      // SD卡离线
  6#define E_ACCESS_RFU        3      // 拒绝访问
  7#define E_REQUEST           4      // 请求错误
  8#define E_VER_UMATCH        5      // 版本不匹配
  9#define E_NO_TOPIC          6      // Topic未实现
 10#define E_IN_USB            7      // 正处于USB模式
 11#define E_IN_VIDEO          8      // 正在录像
 12#define E_IN_BROWSE         9      // 正在浏览模式
 13#define E_IN_PARKING        10     // 正在停车
 14#define E_OPEN_FILE         11     // 打开文件失败
 15#define E_SYS_EXCEP         12     // 系统异常
 16#define E_NET_ERR           14     // 网络异常
 17#define CTP_PULL_OFFLINE    15     //后拉不在线
 18#define CTP_PULL_NOSUPPORT  16     //后拉不支持
 19#define CTP_RT_OPEN_FAIL    17     //实时流打开失败
 20
 21#pragma mark <- 流媒体头部 ->
 22
 23#define PCM_TYPE_AUDIO               1      //音频数据
 24#define JPEG_TYPE_VIDEO              2      //视频jpg格式
 25#define H264_TYPE_VIDEO              3      //视频h264格式
 26#define PREVIEW_TYPE                 4      //预览图
 27#define DATE_TIME_TYPE               5      //用于时间同步包
 28#define MEDIA_INFO_TYPE              6      // 视频媒体信息
 29#define PLAY_OVER_TYPE               7      //结束包
 30
 31#pragma mark <- topic说明 ->
 32//topic说明
 33#define DV_APP_ACCESS      @"APP_ACCESS"            //APP访问
 34#define DV_KEEP_ALIVE      @"CTP_KEEP_ALIVE"        //心跳包
 35#define DV_KEEP_INTERVAL   @"KEEP_ALIVE_INTERVAL"   //心跳间隔时间
 36#define DV_SD_STATUS       @"SD_STATUS"             //SD卡状态
 37#define DV_BAT_STATUS      @"BAT_STATUS"            //电池状态
 38#define DV_UUID            @"UUID"                  //设备UUID
 39#define DV_KEY_VOICE       @"KEY_VOICE"             //按键声音
 40#define DV_LIGHT_FRE       @"LIGHT_FRE"             //光源频率
 41#define DV_AUTO_SHUTDOWN   @"AUTO_SHUTDOWN"         //自动关机
 42#define DV_SCREEN_PRO      @"SCREEN_PRO"            //屏幕保护
 43#define DV_LANGUAGE        @"LANGUAGE"              //语言设置
 44#define DV_TV_MODE         @"TV_MODE"               //TV模式
 45#define DV_FORMAT          @"FORMAT"                //格式化
 46#define DV_SYSTEM_DEFAULT  @"SYSTEM_DEFAULT"        //默认设置
 47#define DV_VIDEO_PARAM     @"VIDEO_PARAM"           //录像参数(前视)
 48#define DV_PULL_VIDEO_PARAM @"PULL_VIDEO_PARAM"     //录像参数(后视)
 49#define DV_VIDEO_CTRL      @"VIDEO_CTRL"            //录像控制
 50#define DV_VIDEO_FINISH    @"VIDEO_FINISH"          //录像完成
 51#define DV_DOUBLE_VIDEO    @"DOUBLE_VIDEO"          //双路录像
 52#define DV_VIDEO_LOOP      @"VIDEO_LOOP"            //循环录像
 53#define DV_VIDEO_WDR       @"VIDEO_WDR"             //夜视增强
 54#define DV_VIDEO_EXP       @"VIDEO_EXP"             //曝光补偿
 55#define DV_MOVE_CHECK      @"MOVE_CHECK"            //移动侦测
 56#define DV_VIDEO_MIC       @"VIDEO_MIC"             //录音设置
 57#define DV_VIDEO_DATE      @"VIDEO_DATE"            //视频日期标签
 58#define DV_VIDEO_CAR_NUM   @"VIDEO_CAR_NUM"         //车牌设置
 59#define DV_GRA_SEN         @"GRA_SEN"               //重力感应
 60#define DV_VIDEO_PAR_CAR   @"VIDEO_PAR_CAR"         //停车监控
 61#define DV_VIDEO_INV       @"VIDEO_INV"             //间隔录影
 62#define DV_PHOTO_RESO      @"PHOTO_RESO"            //拍照分辨率
 63#define DV_PHOTO_CTRL      @"PHOTO_CTRL"            //拍照控制
 64#define DV_SELF_TIMER      @"SELF_TIMER"            //设置定时拍照时间
 65#define DV_BURST_SHOT      @"BURST_SHOT"            //连拍
 66#define DV_PHOTO_QUALITY   @"PHOTO_QUALITY"         //图像质量
 67#define DV_PHOTO_SHARPNESS @"PHOTO_SHARPNESS"       //图像锐度
 68#define DV_WHITE_BALANCE   @"WHITE_BALANCE"         //图像白平衡
 69#define DV_PHOTO_ISO       @"PHOTO_ISO"             //图像ISO
 70#define DV_PHOTO_EXP       @"PHOTO_EXP"             //曝光补偿
 71#define DV_ANTI_TREMOR     @"ANTI_TREMOR"           //防手抖
 72#define DV_PHOTO_DATE      @"PHOTO_DATE"            //图片日期标签
 73#define DV_BOARD_VOICE     @"BOARD_VOICE"           //开机音效
 74#define DV_FAST_SCA        @"FAST_SCA"              //快速预览
 75#define DV_PHOTO_COLOR     @"PHOTO_COLOR"           //色彩设置
 76#define DV_LAST_VIDEO_TIME @"LAST_VIDEO_TIME"       //剩余录像时间
 77#define DV_LAST_PHOTO_NUMS @"LAST_PHOTO_NUMS"       //剩余拍照数
 78#define DV_DIGITAL_ZOOM    @"DIGITAL_ZOOM"          //数字变焦
 79#define DV_DATE_TIME       @"DATE_TIME"             //日期时间
 80#define DV_AP_SSID_INFO    @"AP_SSID_INFO"          //AP账号信息
 81#define DV_STA_SSID_INFO   @"STA_SSID_INFO"         //STA账号信息
 82#define DV_CLOSE_WIFI      @"CLOSE_WIFI"            //关闭WIFI热点
 83#define DV_RESET           @"RESET"                 //复位
 84#define DV_FIRMWARE_UPDATE @"FIRMWARE_UPDATE"       //固件升级
 85#define DV_GEN_CHK         @"GEN_CHK"               //重力感应检测命令
 86#define DV_VIDEO_BUNMPING  @"VIDEO_BUMPING"         //碰撞截取命令
 87#define DV_TF_CAP          @"TF_CAP"                //TF_Card 容量
 88#define DV_RT_TALK_CTL     @"RT_TALK_CTL"           //设置实时对讲
 89#define DV_FILE_LOCK       @"FILE_LOCK"             //文件加锁、解锁设置
 90#define DV_RTF_RES         @"RTF_RES"               //前置实时流分辨率
 91#define DV_RTB_RES         @"RTB_RES"               //后置实时流分辨率
 92#define DV_APP_VIDEO_REC   @"APP_VIDEO_REC"         //设备主动请求APP录像(无卡录影)
 93#define DV_APP_TAKE_PHOTO  @"APP_TAKE_PHOTO"        //设备主动请求APP拍照(无卡拍照)
 94#define DV_CTP_CLI_DISCONNECT    @"CTP_CLI_DISCONNECT"    //关闭TCP连接
 95#define DV_VIDEO_CYC_SAVEFILE    @"VIDEO_CYC_SAVEFILE"    //循环保存文件
 96#define DV_GENERIC_CMD           @"GENERIC_CMD"           //自定义命令
 97
 98///设备WiFi模组相关
 99#define DV_WIFI_MODE             @"WIFI_MODE"             //获取WIFI模组支持频道和设置频道
100#define DV_WIFI_RUN_MODE         @"WIFI_RUN_MODE"         //获取WIFI模组当前正在运行频道
101
102
103///飞控命令(选配)需要设备支持
104#define DV_WIND_VELOCITY                @"WIND_VELOCITY"                //获取风速等级
105#define DV_DEVICE_DIRECTION_CONTROL     @"DEVICE_DIRECTION_CONTROL"     //控制设备飞行方向
106
107//实时流模块
108#define DV_OPEN_PULL_RT_STREAM   @"OPEN_PULL_RT_STREAM"   //打开后拉实时流
109#define DV_CLOSE_PULL_RT_STREAM  @"CLOSE_PULL_RT_STREAM"  //关闭后拉实时流
110#define DV_OPEN_RT_STREAM        @"OPEN_RT_STREAM"        //打开主摄像实时流
111#define DV_CLOSE_RT_STREAM       @"CLOSE_RT_STREAM"       //关闭主摄像实时流
112#define DV_PULL_VIDEO_STATUS     @"PULL_VIDEO_STATUS"     //后拉实时流状态检测
113#define DV_REAL_STREAM_ERROR     @"REAL_STREAM_ERROR"     //实时流中返回的错误
114#define DV_REAL_DROP_FRAME       @"REAL_DROP_FRAME"       //实时流中个丢帧的错误返回
115
116
117
118
119#define DV_OPEN_PHONE_MODE       @"OPEN_PHONE_MODE"       //打开对讲模式
120#define DV_CLOSE_PHONE_MODE      @"CLOSE_PHONE_MODE"      //关闭对讲模式
121
122#define DV_BROWSE_MODE               @"BROWSE_MODE"              //浏览模式
123#define DV_FORWARD_MEDIA_FILES_LIST  @"FORWARD_MEDIA_FILES_LIST" //前视媒体文件列表
124#define DV_BEHIND_MEDIA_FILES_LIST   @"BEHIND_MEDIA_FILES_LIST"  //后视媒体文件列表
125#define DV_THUMBNAILS                @"THUMBNAILS"               //浏览缩略图
126#define DV_THUMBNAILS_CTRL           @"THUMBNAILS_CTRL"          //浏览缩略图控制
127#define DV_MULTI_COVER_FIGURE        @"MULTI_COVER_FIGURE"       //多文件封面图
128#define DV_FILES_DELETE              @"FILES_DELETE"             //文件删除
129
130#define DV_TIME_AXIS_PLAY            @"TIME_AXIS_PLAY"           //时间轴播放
131#define DV_TIME_AXIS_PLAY_CTRL       @"TIME_AXIS_PLAY_CTRL"      //时间轴播放控制
132#define DV_TIME_AXIS_FAST_PLAY       @"TIME_AXIS_FAST_PLAY"      //时间轴快速播放
133#define DV_TIME_AXIS_PLAY_STREAM     @"TIME_AXIS_PLAY_STREAM"    //时间轴播放码流
134
135#pragma mark<- 缩略图 ->
136#define DV_MEDIADOWNLOAD_OK          @"MEDIADOWNLOAD_OK"         //视频缩略图数据下载完毕通知
137#define DV_ALL_MEDIADOWN_OK          @"MEDIADOWNLOAD_ALL_OK"     //下载的缩略图全部Over
138#define DV_AVI_MEDIAIMAGE_OK         @"AVI_MEDIAIMAGE_OK"        //本地AVI文件录制完成后的缩略图通知
139
140#pragma mark<- 视频多张预览图 ->
141#define DV_MEDIASPHOTO_GET           @"MEIDASPHOTOS_GET"         //获取到单个视频的某一张预览图
142#define DV_MEDIASPHOTO_FINISH        @"MEIDASPHOTOS_FINISH"      //所需要预览图已全部获取完毕
143
144#define DV_SUMMARY_NOTE                 @"DV_SUMMARY_NOTE_SEND"       //
145
146#define DV_PCM_DATA                     @"MEDIA_PCM_DATA"         //PCM 数据通知
147#define DV_VIDEO_DATA_STREAM            @"MEDIA_VIDEO_DATA"       //H264数据或者JPEG数据(固件决定)
148#define DV_VIDEO_IMAGE_STREAM           @"GET_UIIMAGE_IJKPLAYER"  //获取当前这一刻的视频图像:UIImage(计划在后续版本中去掉,获取方法IJKMediaPlayback.h 的 thumbnailImageAtCurrentTime)
149#define DV_PCM_DATA_B                   @"MEDIAPCM_BLACK"         //回放时的 PCM 数据
150#define DV_VIDEO_DATA_B                 @"MEDIAVIDEO_BLACK"       //回放时的 视频 数据
151#define DV_STATES_DATA_B                @"MEDIA_STATES_DATA_B"    //缓冲不足时的返回,NSObject:1=缓存不足,2 = 结束缓冲(有足够的播放数据)3 = 没有缓冲
152#define DV_DATE_TIME_B                  @"MEDIADATE_DATE"         //回放时的时间戳 NSString
153#define DV_MAP_GPS                      @"MAP_GPS_DATA"           //视频对应GPS数据 Object: JLGPSPointParam
154#define DV_MAP_NO_GPS                   @"MAP_GPS_NO_DATA"        //视频对应没有GPS数据
155#define DV_MEDIA_INFO_B                 @"MEDIA_INFO_B"           //即将播放的媒体文件的信息
156#define DV_FILE_PLAYBACK_FINISH         @"FILE_PLAYBACK_FINISH"   //当前回放着的文件已经缓冲完毕
157#define DV_ONE_FILE_PLAYBACK_FINISH     @"ONE_FILE_PLAY_FINISH"   //某一个文件回放完毕通知
158#define DV_RECIVE_TIMEOUT               @"RECIVE_TIME_OUT"        //回放文件时超时
159#define DV_PLAYBACK_ERROR               @"PLAYBACK_ERROR_NOTE"    //回放时错误通知(后面带错误类型NSDictionary类型)
160#define DV_APP_SET_PROJECTION           @"NET_SCR"     //设置手机投影功能
161#define DV_DEV_REQ_PROJECTION           @"DEV_REQ_PROJECTION"     //设备请求APP设置手机投影功能,该命令是设备主动发给APP
162#define DV_REALSTREAM_STATUS            @"REALSTREAM_STATUS"      //传输状态变更通知
163
164
165#define DV_PLAYBLACK_OVER               @"MEDIA_PLAYBLACK_OVER"   //收到回放数据最后一包,准备结束回放视频
166#define DV_URGENT_VIDEO_OK              @"URGENT_VIDEO_OK"        //录制紧急视频完成后的通知
167#define DV_BANDWIDTH                    @"BANDWIDTH"              //带宽
168#define DV_UX_REPORT                    @"UX_REPORT_LOSE"         //丢包统计
169
170#pragma mark <- TCP管理 ->
171//tcp 地址管理
172#define DV_TCP_ADDRESS     [[JLDV16SDK shareInstanced] tcpIp] //@"192.168.1.1"   //TCP 地址//""192.168.9.169//@"192.168.9.137" //
173#define DV_TCP_DEFORE_A    @"192.168.1.1"
174#define DV_TCP_PORT        3333             //TCP 端口
175#define DV_TCP_STREAM      2229             //TCP 实时流端口
176#define DV_PLAYBLACK_PORT  2223             //回看 端口
177#define DV_HTTP_PORT       8080             //Http 端口
178#define DV_SOCKETER_PORT   2226             //Media tcp下载端口
179#define DV_COLLISION_PORT  2229             //碰撞截取下载端口
180#define DV_PCM_PORT        2231             //PCM数据发送端口
181#define DV_UDP_PORT        2228             //UDP数据发送端口
182#define DV_UDP_JPG_PORT    2230             //手机投屏的端口
183#define DV_TCP_PULL_PORT   2233             //双路TCP后拉端口
184
185#pragma mark <- 服务发现 ->
186#define DV_DISCOVERY_PORT  3889              //服务发现端口
187#define DV_MSSDP_PORT      3889              //服务发现端口2
188#define DV_DISCOVERY_MSG   @"MSSDP_SEARCH"   //服务发现信息内容
189#define DV_DISCOVERY_NOTI  @"MSSDP_NOTIFY"    //服务发现回调
190
191
192//tcp连接信息
193#define DV_TCP_CONNECTED            @"TCP_CONNECTED"       //TCP 连接成功
194#define DV_TCP_DISCONNECTED         @"TCP_DISCONNECTED"    //TCP 断开连接
195#define DV_TCP_TIMEOUT              @"TCP_TIMEOUT"         //TCP 超时
196#define DV_TCP_STREAM_CONNECTED     @"ECTCPCONNECT_OK"     //TCP Stream 连接成功
197#define DV_TCP_STREAM_DISCONNECTED  @"DV_TCP_STREAM_DISCONNECTED" //TCP Stream 断开连接
198
199//AppInfo
200#define APP_SDK_VERSION     @"2.5.9"                        //SDK版本
201#define APP_DV_VERSION      [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]
202
203//APP状态管理通知
204#define APP_DV_UNACTIVE    @"SCREEN_WILL_UN_ACTIVE"         //变得不活跃
205#define APP_DV_BEACTIVE    @"SCREEN_WILL_BE_ACTIVE"         //变得活跃

2.2 命令控制功能相关

JLCtpSender.h 是一个命令收发的单例类,主要是通过与固件建立 TCP 通讯连接,收发控制命令。具体的 Topic 命令可参考类:JLDV16SDK.h 中的描述,具体的消息返回会通过通知的形式返回,参数的说明可参考附录。

这里需要先初始化单例([[JLCtpSender sharedInstanced] ….])使用流程是:先连接上固件服务器,当收到一系列的回复之后,在进行控制操作;

如使用UDP传输则需要new一个对象 -(instancetype)initWithUdpCmd; 来实现.

心跳包如果想改变发送方式(TCP、UDP)则可以通过方法: -(void)dvReSetHeartSend:(NSTimeInterval )sendTime WithTimeOut:(NSTimeInterval)heartTimeOut WithType:(BOOL)type 重置心跳包发送。

 1//部分接口代码展示
 2/**
 3超时时间
 4*/
 5@property (nonatomic,assign)NSTimeInterval  jlTimeOut;
 6@property (nonatomic,assign)BOOL            isConnect;
 7+ (instancetype)sharedInstanced;
 8
 9
10/**
11创建一个udp通讯socket
12//UDP 通讯不建议做心跳包,因为协议的原因,丢包时心跳丢失。
13@return udp object
14*/
15-(instancetype)initWithUdpCmd;
16
17/**
18重置心跳包发送间隔以及超时时间
19(超时时间是指,在手机发出心跳包到收到回复之间的时间段,如果超过设置时间还没回复则视为超时断开)
20@param sendTime 心跳包间隔
21@param heartTimeOut 超时时间
22@param feq 允许超时次数(1~10)默认为1次
23@param type 心跳包发送方式:YES:TCP  NO:UDP
24*/
25-(void)dvReSetHeartSend:(NSTimeInterval )sendTime WithTimeOut:(NSTimeInterval)heartTimeOut TimeOutFrequency:(NSInteger)feq WithType:(BOOL)type;
26/**
27自定义发送心跳接口
28本接口会停掉自动发送心跳,需要根据开发者自定义规则去发送心跳包
29*/
30-(void)dvDidSendHeartBeat;
31
32/**
33停止默认的心跳发送(不可在收到接入命令DV_KEEP_ALIVE_INTERVAL之前调用,不然会失效)
34*/
35-(void)dvDidStopSendHeartBeat;
36
37/**
38连接tcp服务器
39
40@param host 地址
41@param port 端口
42*/
43- (void)didConnectToAddress:(NSString *)host withPort:(NSInteger)port;
44/**
45断开连接
46*/
47- (void)desConnectedCTP;

2.2.1 设备的STA/AP模式切换

由于设备所具备的WiFi模块是同时支持STA(连接到路由的外围设备)和AP(作为路由器)的,所以在某些业务的时候需要进行切换,具体的操作如下:

AP —–> STA

  1. 在AP模式下手机端通过接口 JLCtpSender 类,发送请求 dvSetSTAssidInfoWith:(NSString *)ssid password:(NSString *)psw onStatus:(int)status ,把小机切换到STA模式

  2. 手机端连入与小机同一个局域网,然后启动 JLDiscoveryService 服务发现类的方法:jlDiscoverServiceDevice ,然后切换连接IP地址,设置`JLDV16SDK.h` 类的tcpIp属性`[[JLDV16SDK shareInstanced] setTcpIp:@”新的IP地址”]` ,然后的流程和AP模式的一样连接以及控制。

STA——>AP

  1. 在STA模式下,进入手机设置然后发送`JLCtpSender`类的,- (void)dvDefaulteSetWithStatus:(int)status,小机进入重启之后就可以重启APP,进行连接。

2.2.2 心跳包机制

CTP命令通道的Socket是依靠心跳收发来维持连接。已有默认设置为:

手机端发起心跳请求,设备端收到回复。

手机端发送频率的依据收到设备端发出的Topic: DV_KEEP_ALIVE_INTERVAL

1心跳包间隔回复:{
2op = NOTIFY;
3    param =     {
4        timeout = 30000;
5    };
6}

timeout 为心跳超时时间,所以手机端发送间隔最大不要超过这个间隔,否则设备就认为收不到心跳而心跳超时。 默认的心跳间隔为: timeout/1000/5 默认的心跳超时为: timeout

如果需要自定义心跳发送,则需要在收到DV_KEEP_ALIVE_INTERVAL之后执行重置的方法

1/**
2重置心跳包发送间隔以及超时时间
3(超时时间是指,在手机发出心跳包到收到回复之间的时间段,如果超过设置时间还没回复则视为超时断开)
4@param sendTime 心跳包间隔
5@param heartTimeOut 超时时间
6@param feq 允许超时次数(1~10)默认为1次
7@param type 心跳包发送方式:YES:TCP  NO:UDP
8*/
9-(void)dvReSetHeartSend:(NSTimeInterval )sendTime WithTimeOut:(NSTimeInterval)heartTimeOut TimeOutFrequency:(NSInteger)feq WithType:(BOOL)type;

如果需要根据其他需求进行判断和发送心跳可以这样: 当收到 DV_KEEP_ALIVE_INTERVAL 之后,进入其通知,先执行停止方法

1/**
2停止默认的心跳发送(不可在收到接入命令DV_KEEP_ALIVE_INTERVAL之前调用,不然会失效)
3*/
4-(void)dvDidStopSendHeartBeat;

发出心跳的方法如下:

1/**
2自定义发送心跳接口
3本接口会停掉自动发送心跳,需要根据开发者自定义规则去发送心跳包
4*/
5-(void)dvDidSendHeartBeat;

收到心跳的通知为:@”DV_KEEP_ALIVE”

开发者可进行自定义判断断开机制和流程。

2.2.3 其他控制类命令

其他的控制命令可参考 JLCtpSender.h

参考部分代码如下:

  1/**
  2根据topic查询对应的状态
  3
  4@param topic Topic
  5*/
  6-(void)checkDeviceStatusWithTopic:(NSString *)topic;
  7
  8#pragma mark <- 设置 ->
  9/**
 10设备按键声音
 11
 12@param status 开关
 13*/
 14-(void)dvKeyVoiceSetWith:(BOOL)status;
 15
 16/**
 17设置设备自动关机
 18
 19@param status 参数:0:off,1:3m,2:5m,3:10m
 20*/
 21-(void)dvAffterAutoShoudown:(int)status;
 22
 23/**
 24设置屏幕保护
 25
 26@param pro 参数:0: off,1:30s 2:1m 3:2m
 27*/
 28-(void)dvScreenProtect:(int)pro;
 29
 30/**
 31设置设备语言
 32
 33@param status 参数:1:简体中文,2:繁体中文 3:日文 4:韩文  5:英文 6:法文 7:德语  8:意大利语 9:荷兰语 10 葡萄牙语
 34*/
 35-(void)dvLangagueSetWith:(int)status;
 36
 37
 38/**
 39设置设备TV模式
 40
 41@param status 参数:0:pal,1:ntsc
 42*/
 43-(void)dvTvModelSetWith:(int)status;
 44
 45
 46/**
 47设置设备格式化
 48
 49*/
 50-(void)dvFormateWithStatus;
 51
 52
 53/**
 54设置设备录像参数
 55
 56@param type 视频格式:0:JPEG,1:H264
 57@param height 分辨率高
 58@param width 分辨率宽
 59@param fps 帧数
 60@param rate 音频采样率
 61@param around 前后视:0:前视  1:后视
 62*/
 63-(void)dvVideoParamSetWithFormate:(int)type Height:(int)height Width:(int)width Fps:(int)fps Rate:(int) rate Around:(int) around;
 64
 65/**
 66手机控制设备启动/关闭录像。
 67
 68@param status 状态(0:关闭, 1:打开)
 69*/
 70- (void)dvVideoControlWithStatus:(int)status;
 71
 72/**
 73设置双路录像
 74
 75@param status 状态(0:off, 1:on)
 76*/
 77- (void)dvVideoDoubleWithStatus:(int)status;
 78
 79/**
 80设置循环录像
 81
 82@param status  状态(0:关闭,1:3m, 2:5m, 3:10m) (m :分钟)
 83*/
 84-(void)dvVideoLoopWithStatus:(int)status;
 85
 86
 87/**
 88设置夜视增强
 89
 90@param status 状态(0:关闭, 1:打开)
 91*/
 92-(void)dvVideoNightVersionWithStatus:(int)status;
 93
 94
 95/**
 96设置视频曝光补偿
 97
 98@param status 状态:(0:+2 1:+5/3 2:+4/3  3:+1 4:+2/3 5:+1/3 6:0 7:-1/3 8:-2/3 9:-1 10:-4/3 11:-5/3 12:-2 )
 99*/
100-(void)dvVideoExposureWithStatus:(int)status;
101
102
103/**
104设置移动侦测
105
106@param status 状态(0:关闭, 1:打开)
107*/
108-(void)dvVideoMoveCheckWithStatus:(int)status;
109
110/**
111设置录音设置
112
113@param status 状态(0:关闭, 1:打开)
114*/
115-(void)dvVideoRecordMicSetWith:(int)status;
116/**
117设置视频日期标签
118
119@param status 状态(0:关闭, 1:打开)
120*/
121-(void)dvVideoDateSetWithStatus:(int)status;
122
123/**
124设备车牌设置
125
126@param status 状态(0:关闭, 1:打开)
127*/
128-(void)dvVideoCarNumberSetWith:(int)status;
129
130/**
131设置重力感应
132
133@param status 状态(0:关闭,1:low, 2:med, 3:hig)
134*/
135-(void)dvGravitySensorWith:(int)status;
136
137/**
138设置停车监控
139
140@param status 状态(0:关闭, 1:打开)
141*/
142-(void)dvParkingGuardWithStatus:(int)status;
143
144/**
145设置间隔录影
146
147@param status 状态(0:关闭,1:100ms, 2:200ms, 3:500ms)
148*/
149-(void)dvVideoIntervalWithStatus:(int)status;
150
151/**
152设置拍照分辨率
153
154@param formate 拍照分辨率(0:vga, 1:1.3M, 2:2M ,3:3M ,4:5M, 5:8M, 6:10M, 7:12M)
155*/
156-(void)dvPhotoResolutionWithFormate:(int)formate;
157
158/**
159拍照命令,当设备拍照完成后,会返回照片文件名给APP。
160*/
161-(void)dvPhotoControl;
162/**
163设置定时拍照时间
164
165@param status 定时拍照选项(0:关 1:定时2秒 2:定时5秒 3:定时10秒)
166*/
167-(void)dvPhotoSetSelfTimerWithStatus:(int)status;
168/**
169连拍设置
170
171@param Index 连拍张数(0:关  1:3张  2: 5张  3:10张)(备注2 3 没实现)
172*/
173-(void)dvPhotoBurstShotWithIndex:(int)Index;
174
175
176/**
177设置图像质量
178
179@param Index 质量(0:lo  1:md  2: hi  )
180*/
181-(void)dvPhotoQualitySetWithIndex:(int)Index;
182
183/**
184设置图像锐度
185
186@param Index 锐度(0:lo  1:md  2: hi  )
187*/
188-(void)dvPhotoShrpnessSetWithIndex:(int)Index;
189
190
191/**
192设置图像白平衡
193
194@param Index 白平衡(0:自动  1:日光  2:阴天  3:钨丝灯  4:荧光灯)
195*/
196-(void)dvPhotoWhileBlanceWithIndex:(int)Index;
197
198/**
199设置图像ISO
200
201@param Index iso(0:自动  1:100  2:200  3:400 )
202*/
203-(void)dvPhotoISOsetWithIndex:(int)Index;
204
205
206/**
207设置图像曝光补偿
208
209@param Index 曝光参数 (0:+2 1:+5/3 2:+4/3  3:+1 4:+2/3 5:+1/3 6:0 7:-1/3 8:-2/3 9:-1 10:-4/3 11:-5/3 12:-2 )
210*/
211-(void)dvPhotoExposureWithIndex:(int)Index;
212
213/**
214设置防手抖
215
216@param status 状态(0: off  1: on )
217*/
218-(void)dvAntiTremorSetWithStatus:(int)status;
219
220/**
221设置照片日期标签
222
223@param status 状态(0: off  1: on )
224*/
225-(void)dvPhotoDateShowWithStatus:(int)status;
226
227/**
228设置开机音效
229
230@param status 状态(0: off  1: on )
231*/
232-(void)dvBoardVoiceSwitch:(int)status;
233
234/**
235快速预览设置
236
237@param Index sca( 0: off  1:2s 2:5s)
238*/
239-(void)dvPhotoPreviewFasterWithIndex:(int)Index;
240
241/**
242色彩设置
243
244@param Index Color ( 0: off  1:黑白 2:陈旧)
245*/
246-(void)dvPhoneColorSetWithIndex:(int)Index;
247
248/**
249设置默认设置
250
251@param status 参数:0:no,1:yes
252*/
253- (void)dvDefaulteSetWithStatus:(int)status;
254
255/**
256查询剩余录像时间
257*/
258-(void)dvGetLastVideoTime;
259/**
260查询剩余拍照张数
261*/
262-(void)dvGetLastPhotoNumber;
263/**
264数字变焦
265
266@param type 方式(0:缩小  1:放大)
267@param multiple 放大倍数(大于等于1)
268*/
269-(void)dvDigitalZoomSetWith:(int)type Multiple:(float)multiple;
270/**
271日期时间
272
273@param dateStr 必须严格按照格式:yyyy-MM-dd-HH-mm-ss
274*/
275- (void)dvDateTimeSet:(NSString *)dateStr;
276
277/**
278设置 AP 账号信息
279
280@param ssid AP模式SSID
281@param psw AP模式PWD
282@param status 是否立即生效(0:否  1:是)
283*/
284-(void)dvSetAPssidInfoWith:(NSString *)ssid Password:(NSString *)psw onStatus:(int)status;
285/**
286设置 STA 账号信息
287
288@param ssid 路由器的SSID
289@param psw  路由器的PWD
290@param status 是否保存路由器信息(0:否  1:是)
291*/
292-(void)dvSetSTAssidInfoWith:(NSString *)ssid password:(NSString *)psw onStatus:(int)status;
293/**
294关闭WiFi热点
295*/
296-(void)dvCloseDeviceWiFi;
297/**
298复位
299*/
300-(void)dvMakeDeviceReset;
301/**
302固件升级
303*/
304-(void)dvFirmwareUpdateReq;
305/**
306重力感应检测命令
307*/
308-(void)dvGravitysensorCheck;
309/**
310碰撞截取命令
311*/
312-(void)dvCollisionInterception;
313
314/**
315开启循环保存文件
316**
317保存当前录像文件、开启下一个录像文件
318*/
319-(void)dvSaveTheFileInALoop;
320
321/**
322获取TFCard剩余容量
323*/
324-(void)dvCheckDeviceTFCap;
325
326/**
327设置的实时对讲功能
328
329@param status 状态(0:关闭, 1:打开)
330*/
331-(void)dvIntercomControl:(int)status;
332
333/**
334发命令让小机断开CTP连接
335*/
336-(void)dvCutOffCtpAisle;
337
338/**
339文件加解锁设置
340
341@param status 1:加锁   0:解锁
342@param path 小机文件路径
343
344*/
345-(void)dvVideoFileLockControl:(int)status WithPath:(NSString *)path;

2.3 视频缩略图模块

缩略图接收类: JLMediaSocketer.h 获取缩略图的请求是由 JLCtpSender.h 所发起通过:dvGetMultiFileCoverMap: 请求获取

由于获取到的缩略图实际为视频帧的I帧,所以在解析的过程中需要利用IJKMediaPlayer里头的ffmpeg。

视频获取缩略图的流程可参考以下内容:

  1//从已下载的vf_list.txt文件中读取信息下载
  2NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) lastObject];
  3path = [path stringByAppendingPathComponent:@"vf_list.txt"];
  4NSData *data = [NSData dataWithContentsOfFile:path];
  5if (data) {
  6    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
  7    NSArray *imgArray = dict[@"file_list"];
  8    NSMutableArray *TargetArray = [NSMutableArray array];
  9
 10    for (NSDictionary *tmpDict in imgArray) {
 11        NSString *fileName = tmpDict[@"f"];
 12        if ([fileName hasSuffix:@".mov"]) {
 13            [dataArray addObject:tmpDict];
 14            [TargetArray addObject:fileName];
 15        }
 16    }
 17
 18    [[JLCtpSender sharedInstanced] dvGetMultiFileCoverMap:TargetArray];
 19    [[JLMediaSocketer sharedInstance] startToReceiveMediaData:TargetArray];
 20
 21}
 22
 23
 24    //接收通知
 25    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getNewVideoSmallImage:) name:JLFFDECODESU object:nil];
 26    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getFailedVideosmallImage:) name:JLFFDECODEFA object:nil];
 27    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mediaMuLticover:) name:DV_MULTI_COVER_FIGURE object:nil];
 28    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mediaVideoFileLoadOK:) name:DV_MEDIADOWNLOAD_OK object:nil];
 29
 30
 31
 32    -(void)getNewVideoSmallImage:(NSNotification *)note{
 33
 34NSDictionary *dict = [note object];
 35
 36NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
 37path = [path stringByAppendingString:@"/Medias/Thumbnails"];
 38path = [path stringByAppendingPathComponent:dict[@"filename"]];
 39path = [path stringByReplacingOccurrencesOfString:@".mov" withString:@".movps"];
 40[DFFile createPath:path];
 41NSData *data = dict[@"image"];
 42[DFFile writeData:data fillFile:path];
 43
 44//    UIImageView *test = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
 45//    test.image = [UIImage imageWithData:dict[@"image"]];
 46//    [self.view addSubview:test];
 47
 48
 49[collections reloadData];
 50
 51}
 52-(void)getFailedVideosmallImage:(NSNotification *)note{
 53
 54NSDictionary *dict = note.object;
 55NSLog(@"error:%@ withName:%@",dict[@"err_msg"],dict[@"filename"]);
 56
 57}
 58
 59
 60-(void)mediaVideoFileLoadOK:(NSNotification *)note{
 61
 62NSDictionary *dataDict = [note object];
 63
 64if ([dataDict[@"file"] hasSuffix:@"AVI"]) {//区分是不是AVI文件的缩略图
 65
 66        NSDictionary *tmpDict = @{@"file":dataDict[@"file"],@"image":dataDict[@"iData"]};
 67        [self saveAsAviPrivates:tmpDict];
 68
 69    }else{
 70        NSDictionary *dict = @{@"height":dataDict[@"height"],@"width":dataDict[@"width"],@"name":dataDict[@"file"]};
 71        [ffcodeFrame jlFFonFrameCodeWith:dataDict[@"iData"] withDict:dict];
 72
 73    }
 74
 75
 76}
 77
 78-(void)mediaMuLticover:(NSNotification *)note{
 79
 80//    NSLog(@"12345679==>%@",note);
 81
 82
 83}
 84
 85-(void)saveAsAviPrivates:(NSDictionary *)dict {
 86
 87    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
 88    NSFileManager *fm = [NSFileManager defaultManager];
 89    NSString *fileName = dict[@"file"];
 90    path = [path stringByAppendingString:@"/Medias/Thumbnails/"];
 91    if (![fm fileExistsAtPath:path]) {
 92        [fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
 93    }
 94    path = [path stringByAppendingString:fileName];
 95    path = [path stringByReplacingOccurrencesOfString:@".AVI" withString:@".avips"];
 96    [DFFile createPath:path];
 97    NSData *data = dict[@"image"];
 98    [DFFile writeData:data fillFile:path];
 99
100}

本类也用于接收接收多张视频预览图,它主要承担的责任是收集设备过来的缩略图数据,并交给FFmpeg去解析得出缩略图。

下面是接收多张视频缩略图的实例:

 1//        {
 2//            "y":0,              //视频文件类型 0:无效文件 1:普通文件 2:SOS文件 3:延时文件
 3//            "f":"B:/DCIMA/REC00064.MOV",     //文件路径
 4//            "t":"20150807175748",      //生成文件日期
 5//            "d":"00059",
 6//            "h":288,                    //高
 7//            "w":322,          //长
 8//            "p":30,           //帧率
 9//            "s":"000000100"   //16进制表示文件大小
10//            "c":"0"           //0:前视视频 1:后视视频   2:....
11//        }
12
13    int mynowTime = [dict[@"d"] intValue] * sender.value * 1000; //sender.value 是视频文件进度的偏移
14    NSString *mynowTimeStr = [NSString stringWithFormat:@"%d",mynowTime];
15
16
17    //去获取多张预览图
18    [[JLCtpSender sharedInstanced] dvBrowseThumbnails:dict[@"f"] tOffset:mynowTimeStr Timeinv:@"0" number:1];
19    //接收解析预览图数据
20    [thumb downloadMediasPhotoWithNum:1];
21
22    //其他的操作和获取缩略图的步骤一样,通过回调收到预览图...

2.3.1 缩略图数据解析类说明

本类的方法是通过FFmpeg的解析方法来解析,小机所传过来的缩略图数据,转换成jpeg数据。

本类存在于IJKMediaFramework.framework中。

使用示例代码如下:

  1    @interface ThumbnailClass(){
  2
  3        JLFFCodecFrame *ffcodeFrame;
  4        NSArray *ipcDownloadArray;
  5    }
  6
  7    @end
  8
  9    @implementation ThumbnailClass
 10
 11    +(instancetype)sharedInstance{
 12        static ThumbnailClass *thumbMe;
 13        static dispatch_once_t onceToken;
 14        dispatch_once(&onceToken, ^{
 15            thumbMe = [[ThumbnailClass alloc] init];
 16        });
 17        return thumbMe;
 18    }
 19
 20    -(instancetype)init{
 21
 22        self = [super init];
 23
 24        if (self) {
 25
 26            ffcodeFrame = [[JLFFCodecFrame alloc] init];
 27            [self addNote];
 28
 29        }
 30
 31        return self;
 32    }
 33
 34    -(void)downloadWithArray:(NSArray *)downloadArray{
 35
 36
 37        if (downloadArray.count>0) {
 38            NSLog(@"downloadArray:%@",downloadArray);
 39            [[JLCtpSender sharedInstanced] dvGetMultiFileCoverMap:downloadArray];
 40            ipcDownloadArray = downloadArray;
 41        }
 42    }
 43
 44
 45    -(void)downloadMediasPhotoWithNum:(NSInteger)num{
 46
 47        [[JLMediaSocketer sharedInstance] startToReceiveMediasDataForOne:num];
 48
 49    }
 50
 51
 52    /**
 53    获取视频缩略图
 54
 55    @param path 视频文件路径
 56    */
 57    -(void)getVideosThumbnailWith:(NSString *)path{
 58
 59
 60        [ffcodeFrame jlFFonFrameCodeWithVideo:path];
 61
 62    }
 63
 64
 65    - (void)addNote{
 66
 67
 68        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getNewVideoSmallImage:) name:JLFFDECODESU object:nil];
 69        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getFailedVideosmallImage:) name:JLFFDECODEFA object:nil];
 70        [DFNotice add:DV_MULTI_COVER_FIGURE Action:@selector(mediaMuLticover:) Own:self];
 71        [DFNotice add:DV_MEDIADOWNLOAD_OK Action:@selector(mediaVideoFileLoadOK:) Own:self];
 72        [DFNotice add:JLFFDECODESU_VIDEO Action:@selector(videojlCodeSucceed:) Own:self];
 73        [DFNotice add:DV_MEDIASPHOTO_GET Action:@selector(mediasPhotoFileLoadOK:) Own:self];
 74        [DFNotice add:DV_AVI_MEDIAIMAGE_OK Action:@selector(mediaVideoAviFileFinish:) Own:self];
 75
 76    }
 77
 78
 79    -(void)dealloc{
 80        [DFNotice remove:JLFFDECODESU Own:self];
 81        [DFNotice remove:JLFFDECODEFA Own:self];
 82        [DFNotice remove:DV_MULTI_COVER_FIGURE Own:self];
 83        [DFNotice remove:JLFFDECODESU_VIDEO Own:self];
 84        [DFNotice remove:DV_MEDIASPHOTO_GET Own:self];
 85        [DFNotice remove:DV_MEDIASPHOTO_FINISH Own:self];
 86    }
 87
 88    -(void)getNewVideoSmallImage:(NSNotification *)note{
 89
 90        NSDictionary *dict = [note object];
 91
 92        NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
 93        path = [path stringByAppendingString:@"/Medias/Thumbnails"];
 94        path = [path stringByAppendingPathComponent:dict[@"filename"]];
 95        path = [path stringByReplacingOccurrencesOfString:@".MOV" withString:@".movps"];
 96        [DFFile createPath:path];
 97        NSData *data = dict[@"image"];
 98        [DFFile writeData:data fillFile:path];
 99
100        [DFNotice post:THUMB_PATH Object:path];
101
102    }
103
104
105    -(void)videojlCodeSucceed:(NSNotification *)note{
106
107        NSDictionary *dict = [note object];
108        NSFileManager *fm = [NSFileManager defaultManager];
109        NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
110        path = [path stringByAppendingString:@"/video_thum/Thumbnails"];
111        if (![fm fileExistsAtPath:path]) {
112            [fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
113        }
114        path = [path stringByAppendingPathComponent:dict[@"filename"]];
115        path = [path stringByReplacingOccurrencesOfString:@".MOV" withString:@".movps"];
116        [DFFile createPath:path];
117        NSData *data = dict[@"image"];
118        [DFFile writeData:data fillFile:path];
119
120
121
122        NSArray *pathArray = [[path lastPathComponent] componentsSeparatedByString:@"_"];
123        if ([[pathArray lastObject] isEqualToString:@"urgent.movps"]) {
124
125        }else{
126
127            NSArray *pathArray = [dict[@"filePath"] componentsSeparatedByString:@"/"];
128            NSString *newpath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
129            NSString *newpath_1 = [newpath stringByAppendingPathComponent:pathArray[8]];
130            NSString *newpath_2 = [newpath_1 stringByAppendingPathComponent:dict[@"filename"]];
131            NSString *newpath_3 = [newpath_2 stringByReplacingOccurrencesOfString:@".movps" withString:@".MOV"];
132            int dura = [ffcodeFrame jlFFGetVideoDuration:newpath_3];
133
134            NSString *duration = [NSString stringWithFormat:@"_%d.MOV",dura];
135            NSString *newpath_4 = [newpath_3 stringByReplacingOccurrencesOfString:@".MOV" withString:duration];
136
137            [DFFile renameOldName:newpath_3 NewName:newpath_4];
138
139            NSString *path_w = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
140            path_w = [path_w stringByAppendingString:@"/video_thum/Thumbnails"];
141            path_w = [path_w stringByAppendingPathComponent:dict[@"filename"]];
142            NSString *duration_w = [NSString stringWithFormat:@"_%d.movps",dura];
143            path_w = [path_w stringByReplacingOccurrencesOfString:@".MOV" withString:duration_w];
144            [DFFile renameOldName:path NewName:path_w];
145        }
146
147
148    }
149
150    -(void)getFailedVideosmallImage:(NSNotification *)note{
151
152        NSDictionary *dict = note.object;
153
154        NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
155        path = [path stringByAppendingString:@"/video_thum/Thumbnails"];
156        path = [path stringByAppendingPathComponent:dict[@"filename"]];
157        path = [path stringByReplacingOccurrencesOfString:@".MOV" withString:@".movps"];
158        [DFFile createPath:path];
159        UIImage *img = [UIImage imageNamed:@"ImageLoadFailed"];
160        NSData *data = UIImagePNGRepresentation(img);
161        [DFFile writeData:data fillFile:path];
162        NSLog(@"error:%@ withName:%@",dict[@"err_msg"],dict[@"filename"]);
163
164    }
165
166
167
168
169    -(void)mediaVideoFileLoadOK:(NSNotification *)note{
170
171        NSDictionary *dataDict = [note object];
172
173
174        if ([dataDict[@"file"] hasSuffix:@"AVI"]) {
175
176            if ( dataDict[@"iData"] && dataDict[@"file"]) {
177                NSDictionary *tmpDict = @{@"file":dataDict[@"file"],@"image":dataDict[@"iData"]};
178                [self saveAsAviPrivates:tmpDict];
179            }else{
180                NSLog(@"缩略图错误:%@",dataDict);
181            }
182    //        NSLog(@"saveAsAviPrivates:%d",__LINE__);
183
184        }else{
185            if (dataDict[@"height"] && dataDict[@"width"] && dataDict[@"file"]) {
186                NSDictionary *dict = @{@"height":dataDict[@"height"],@"width":dataDict[@"width"],@"name":dataDict[@"file"]};
187                [ffcodeFrame jlFFonFrameCodeWith:dataDict[@"iData"] withDict:dict];
188            }else{
189                NSLog(@"缩略图错误:%@",dataDict);
190            }
191        }
192
193
194
195    }
196
197
198
199    -(void)mediaMuLticover:(NSNotification *)note{
200
201
202        NSDictionary *baseDict = [note object];
203        NSDictionary *parDict = baseDict[@"param"];
204        if ([parDict[@"status"] intValue] == 0 && !baseDict[@"errno"]) {
205            [[JLMediaSocketer sharedInstance] startToReceiveMediaData:ipcDownloadArray];
206    //        NSLog(@"MULTI_COVER_FIGURE:%@",note.object);
207        }
208
209
210    }
211
212
213
214    -(void)saveAsAviPrivates:(NSDictionary *)dict {
215        NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
216        NSFileManager *fm = [NSFileManager defaultManager];
217        NSString *fileName = dict[@"file"];
218        path = [path stringByAppendingString:@"/Medias/Thumbnails/"];
219        if (![fm fileExistsAtPath:path]) {
220            [fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
221        }
222        path = [path stringByAppendingString:fileName];
223        path = [path stringByReplacingOccurrencesOfString:@".AVI" withString:@".avips"];
224
225        [DFFile createPath:path];
226        NSData *data = dict[@"image"];
227        [data writeToFile:path atomically:NO];
228
229    }
230
231
232    #pragma mark <- 获取一个视频的多张缩略图 ->
233
234    -(void)mediasPhotoFileLoadOK:(NSNotification *)note{
235
236        NSDictionary *dataDict = [note object];
237
238        if ([dataDict[@"file"] hasSuffix:@".AVI"]) {
239            if ( dataDict[@"iData"] && dataDict[@"file"]) {
240                NSDictionary *tmpDict = @{@"file":dataDict[@"file"],@"image":dataDict[@"iData"]};
241                [self saveAsAviPrivates:tmpDict];
242            }else{
243                NSLog(@"thumb image error%d:%@",__LINE__,dataDict);
244            }
245        }else{
246            if (dataDict[@"height"] && dataDict[@"width"] && dataDict[@"file"]) {
247                NSLog(@"get thumb image Data:%@",dataDict[@"file"]);
248                NSDictionary *dict = @{@"height":dataDict[@"height"],@"width":dataDict[@"width"],@"name":dataDict[@"file"]};
249                [ffcodeFrame jlFFonFrameCodeWith:dataDict[@"iData"] withDict:dict];
250            }else{
251                NSLog(@"thumb image error%d:%@",__LINE__,dataDict);
252            }
253
254        }
255
256    }
257
258    #pragma mark <- AVI缩略图 ->
259    -(void)mediaVideoAviFileFinish:(NSNotification *)note{
260        NSDictionary *dict = [note object];
261
262        NSString *newpath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
263        NSString *newpath_1 = [newpath stringByAppendingPathComponent:@"video_thum/Thumbnails"];
264        NSString *newpath_2 = [newpath_1 stringByAppendingPathComponent:dict[@"filename"]];
265        NSString *newpath_3 = [newpath_2 stringByReplacingOccurrencesOfString:@".AVI" withString:@".avips"];
266        [DFFile createPath:newpath_3];
267        NSData *data = dict[@"image"];
268        [DFFile writeData:data fillFile:newpath_3];
269        NSLog(@"avi suoluetu");
270    }
271    @end

2.3.2 H264的I帧解码

如需要用到H264中的I帧则可以使用 JLH264ToCVPixelbuffer 类; 数据转换仅仅针对H264的流中的I帧进行解码和转换,当所传入的h264数据不是I帧时不进行解码并返回`nil`值

本类只存在于IJKMediaFramework框架中

当前类只提供了两个API接口,如下:

 1/**
 2把H264的I帧转换成CVPixelBufferRef
 3
 4@param h264 h264数据
 5@param Isize 图片尺码
 6@return CVPixelBufferRef
 7*/
 8-(CVPixelBufferRef )h264DataCovertToRefBuffer:(NSData *)h264 WithSize:(CGSize)Isize;
 9
10/**
11把h264数据的I帧转换成UIimage
12
13@param hData h264数据
14@param Isize 图片尺码
15@return CVPixelBufferRef
16*/
17-(UIImage *)covertH264ToJpeg:(NSData*)hData withCGSize:(CGSize )Isize;

2.3.3 获取视频某个时刻多张预览图

  1. 先需要通过命令FORWARD_MEDIA_FILES_LIST、BEHIND_MEDIA_FILES_LIST 获取设备中所有的文件(视频文件、音频文件)的描述文件vf_list.txt,然后通过http下载该文件,就可得到设备中的文件列表内容。

  2. 通过JLCtpSender类的

1dvBrowseThumbnails:(NSString *)path tOffset:(NSString *)offset1 Timeinv:(NSString *)timeinv number:(int)num;

请求多张预览图,然后通过JLMediaSocketer类接收数据信息。 具体可参考Demo的 ThumbnailClass.h

2.3.4 本地视频缩略图获取

获取本地视频的文件路径,放到 JLFFCodeFrame.h 类中的 jlFFonFrameCodeWithVideo:(NSString *)path 方法。

该方法是个耗时操作,最好放到线程完成,可以通过JLFFDECODESU_VIDEO通知回调,回调内容格式和获取小机那边缩略图是一致的格式。

2.4 视频流处理模块

2.4.1 实时流预览播放

iPhone->Device:op1:请求连接数据传输的Data Socket(TCP/UDP)
Note right of Device:设备端允许连接
Device-->iPhone:告诉手机端已经数据通道已经连接成功
iPhone->Device:op2:通过CTP通道向设备发送请求打开实时流命令
Note right of Device:设备打开实时流,并往Data Socket(TCP/UDP)发送数据
Device-->iPhone:Data:音/视频数据
Note left of iPhone:打开IJKPlayer

这部分的内容可以参考 JLMediaPlayerView.m 的初始化和调用

Op1: 请求连接数据传输的Data Socket(TCP/UDP)

 1int widthInt = 640;
 2int heightInt = 480;
 3int sample_Rate = 8000;
 4int sample_fps = 25;
 5NSInteger streamType = [[JLCtpCaChe sharedInstance] videoStreamType];
 6
 7if ([dict[@"net_type"] isEqualToString:@"0"]) {
 8        //TCP传输
 9        [[JLMediaStreamManager sharedInstance] jlCreateTCPPlayerWithFps:sample_fps SetRate:sample_Rate SetFrame:CGSizeMake(widthInt, heightInt) StreamType:streamType];
10    }else{
11        //默认为UDP
12        [[JLMediaStreamManager sharedInstance] jlCreatePlayerWithType:type SetFps:sample_fps SetRate:sample_Rate SetFrame:CGSizeMake(widthInt, heightInt) StreamType:streamType];
13
14}

OP2: 通过CTP通道向设备发送请求打开实时流命令

 1 int widthInt = 640;
 2int heightInt = 480;
 3int sample_Rate = 8000;
 4int sample_fps = 25;
 5int realType = (int)[[JLCtpCaChe sharedInstance] videoStreamType];
 6
 7if (type == MEDIA_FRONT) {
 8        //前视摄像头
 9        [[JLCtpSender sharedInstanced] dvRealTimeStreamFrontWebcamOpen:realType Width:widthInt Height:heightInt Fps:sample_fps Rate:sample_Rate];
10
11    }else if (type == MEDIA_POST){
12                //后视摄像头
13        [[JLCtpSender sharedInstanced] dvRealTimeStreamPullWebcamOpen:realType Width:widthInt Height:heightInt Fps:sample_fps Rate:sample_Rate];
14}

OP3: 打开ijkPlayer开始播放

 1    -(void)rtspIsBegin:(NSNotification *)note{
 2
 3    [self installMovieNotificationObservers];
 4
 5    [IJKFFMoviePlayerController setLogReport:YES];
 6    [IJKFFMoviePlayerController setLogLevel:k_IJK_LOG_INFO];
 7
 8    IJKFFOptions *options = [IJKFFOptions optionsByDefault];
 9    [self ijkOptionSet:options];
10
11    if (ffmVC == nil) {
12    ffmVC = [[IJKFFMoviePlayerController alloc] initWithContentURLString:self.sdpcontext withOptions:options];
13        [ffmVC prepareToPlay];
14    }else{
15        [ffmVC play];
16        NSLog(@"播放器没准备好");
17        [self reOpenPlayer];
18        return;
19    }
20
21    UIView *playerView = [ffmVC view];
22    playerView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
23    playerView.frame = self.bounds;
24    [self insertSubview:playerView atIndex:1 ];
25    self.autoresizesSubviews = YES;
26    NSString *states = [AppInfoGeneral getValueForKey:JL_VIDEO_MODEL];
27    if ([states isEqualToString:@"WIDTH"]) {
28        ffmVC.scalingMode = IJKMPMovieScalingModeAspectFit;
29    }else{
30        ffmVC.scalingMode = IJKMPMovieScalingModeFill;
31    }
32
33
34}

播放类JLMediaPlayerView

通过初始化这个播放类,可得到一个播放器,用于播放固件那边发过来的实时流数据。本类存在于Demo中,仅供参考使用。

  1. 本地需要添加IJKMediaFrameWork,下载地址: GitHub IJKMediaFramework

  2. 初始化播放类,把它addSubView到ViewController上,在页面离开时要注意关闭和停止实时流,以减少对固件的功耗。

2.4.2 设备文件回放

文件回放模式与实时流预览模式的区别只在于回来的数据通道(端口)不一样,以及请求数据的方式不一样,在对数据进行播放时的处理是一致的。

控制部分的API接口展示

 1/**
 2时间轴播放
 3
 4@param dict 参数字典
 5
 6path: 起始播放文件名
 7
 8offset: 起始播放文件偏移时间(单位:毫秒)
 9
10type: 视频编码格式(0:JPEG, 1:H264)
11
12fps: 视频帧率
13
14w: 视频宽
15
16h: 视频高
17
18*/
19-(void)dvTimeAxisPlay:(NSDictionary *)dict;
20
21/**
22时间轴播放控制
23
24@param status 状态(0:播放,1:暂停,2:停止)
25*/
26-(void)dvTimeAxisPlayControl:(int)status;
27
28
29/**
30时间轴快速播放
31设置时间轴快速播放的倍速等级。
32@param level 等级(0:原速, 1:2倍速, 2:4倍速, 3:8倍速, 4:16倍速, 5:32倍速, 6:64倍速)
33*/
34-(void)dvTimeaxisFastPlay:(int)level;
35
36/**
37时间轴播放码流
38控制时间轴播放的码流。
39@param status 方式(0:加大码流  1:减小码流)
40*/
41-(void)dvTimeAxisPlayStream:(int)status;

Step1:时间轴播放命令 DV_TIME_AXIS_PLAY,让固件准备数据。

1  NSString *typeStr;
2if ([dict[@"f"] hasSuffix:@".MOV"]) {
3    typeStr = @"1";
4}else{
5    typeStr = @"0";
6}
7NSDictionary *targetDict = @{@"path":dict[@"f"],@"offset":offsetStr,@"type":typeStr,@"fps":dict[@"p"],@"w":dict[@"w"],@"h":dict[@"h"]};
8[[JLCtpSender sharedInstanced] dvTimeAxisPlay:targetDict];

Step2:固件准备好后会通过 DV_TIME_AXIS_PLAY 命令返回已经准备好的消息给手机

1[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mediaPlayBlackNote:)name:DV_TIME_AXIS_PLAY object:nil];

Step3:手机收到后,可调用本类方法jlMediaPlayBlackStart开始接收来自固件的数据包

 1-(void)mediaPlayBlackNote:(NSNotification *)note{
 2
 3    NSLog(@"__func%s note:%@",__func__,note.object);
 4    NSDictionary *dict = note.object;
 5    if (dict[@"param"] && !dict[@"errno"]) {
 6        int status = [dict[@"param"][@"status"] intValue];
 7        if (status == 0) {
 8            //开始回放数据
 9            NSDictionary *paramDict;
10            if ([[JLCtpCaChe sharedInstance] rtspStreamType] == YES) {
11                paramDict = [[JLCtpCaChe sharedInstance] videoMsgDict];
12            }else{
13                paramDict = [[JLCtpCaChe sharedInstance] pullVideoDict];
14            }
15            NSInteger streamType = [paramDict[@"format"] integerValue];
16            if (dmMgr.downloadBtnTag == NO) { //非下载模式,启动播放器
17                if (!playerView.ffmVC) {
18                    [[JLMediaPlayBack sharedInstance] jlMediaPlayBackStart:streamType];
19                    [playerView jlPlayBlackMeida];
20                    [self ControlViewDisMiss];
21                }
22            }
23        }
24    }
25}

Step4: 当收到DV_PLAYBLACK_OVER通知(object为空),说明已经回放数据包已经传输完毕。可停止播放。 即可通过代理的方法 @protocol jlMediaPlayBackDelegate <NSObject> 也可通过,监听通知 @"ON_STATE_CHANGE"

2.4.3 视频/音频数据裸流的处理

重要

这里所指的裸数据实质是:H264、JPEG、PCM

在很多时候,由于业务的原因,可能需要对设备发过来的裸流数据进行二次的处理,这时候可以通过以下的做法来达到目的:

2.4.3.1 实时流的裸数据处理

在实时流的播放类 JLMediaStreamManager 中有一个 JLMediaStreamDelegate 代理,通过接收该代理,并实现代理的方法即可进行回调数据的二次处理

 1@optional
 2/// 回调pcm数据
 3/// @param manager 媒体对象
 4/// @param data pcm数据
 5-(void)jlmediaStream:(JLMediaStreamManager *)manager PcmData:(NSData *)data;
 6
 7/// 回调264数据
 8/// @param manager 媒体对象
 9/// @param data 264数据
10-(void)jlmediaStream:(JLMediaStreamManager *)manager H264AndJpegData:(NSData *)data;
11
12/// 回调状态信息
13/// @param manager 媒体对象
14/// @param status 状态
15-(void)jlmedia:(JLMediaStreamManager *)manager StatusChange:(int)status;
16
17/// 错误回调
18/// @param manager 媒体对象
19/// @param error 错误内容代码
20/// @param msg 错误内容描述
21-(void)jlmediaStream:(JLMediaStreamManager *)manager Error:(int)error withMsg:(NSString *)msg;
22
23/// 回调媒体信息包
24/// @param manager 媒体对象
25/// @param data 数据内容
26/// @param type 数据类型
27/// @param seq 序列号
28/// @param ts 时间戳
29-(void)jlmediaStream:(JLMediaStreamManager *)manager Media:(NSData *)data type:(uint32_t)type sequence:(uint32_t)seq timeStamp:(uint32_t)ts;

2.4.3.2 回放的裸数据处理

在回放的播放类 JLMediaPlayBack 中有一个 jlMediaPlayBackDelegate 代理,通过接收该代理,并实现代理的方法即可进行回调数据的二次处理

 1@protocol jlMediaPlayBackDelegate <NSObject>
 2
 3@optional
 4/**
 5音频数据
 6
 7@param data PCM
 8*/
 9-(void)jlmediaPlayBack:(JLMediaPlayBack *)playback PcmData:(NSData *)data;
10
11/**
12视频数据
13
14@param data 视频数据(jpeg or h264)
15*/
16-(void)jlmediaPlayBack:(JLMediaPlayBack *)playback H264AndJpegData:(NSData *)data;
17
18/**
19时间包数据
20
21@param timeStr 时间包
22*/
23-(void)jlmediaPlayBack:(JLMediaPlayBack *)playback TimeRts:(NSString *)timeStr;
24
25/**
26GPS点对象返回
27
28@param param GPS点对象
29*/
30-(void)jlmediaPlayBack:(JLMediaPlayBack *)playback DevGpsType:(JLGPSPointParam *)param;
31
32/**
33当不支持GPS点的时候返回
34*/
35-(void)jlmediaPlayBackGpsNoSupport:(JLMediaPlayBack *)playback;
36
37/**
38缓冲状态返回
39
40@param status 状态
41*/
42-(void)jlmediaPlayBack:(JLMediaPlayBack *)playback NoBuffer:(int)status;
43
44/**
45播放完所有的文件
46*/
47-(void)jlmediaPlayBackEndAllFile:(JLMediaPlayBack *)playback ;
48
49/**
50播放完一个文件
51*/
52-(void)jlmediaPlayBackEndOneFile:(JLMediaPlayBack *)playback;
53
54
55/// 返回媒体数据的内容
56/// @param playback 媒体对象
57/// @param data 媒体数据包
58/// @param type 类型
59/// @param seq seq
60/// @param ts 时间戳
61-(void)jlmediaPlayBack:(JLMediaPlayBack *)playback MediaData:(NSData *)data type:(uint32_t)type sequence:(uint32_t)seq timeStamp:(uint32_t)ts;
62
63/**
64错误信息返回
65
66@param code 错误代码
67@param msg 错误字符串
68*/
69-(void)jlmediaPlayBack:(JLMediaPlayBack *)playback Error:(int)code WithMsg:(NSString *)msg;
70
71/**
72媒体信息包
73
74@param dict 媒体信息字典
75*/
76-(void)jlmediaPlayBack:(JLMediaPlayBack *)playback MediaPack:(NSDictionary *)dict;
77
78/**
79回放状态返回
80
81@param status 状态
82RTS_STATE_IDLE = 0,
83RTS_STATE_PREPARE = 1,
84RTS_STATE_PLAYING = 2,
85RTS_STATE_PAUSE = 4,
86RTS_STATE_END = 5,
87*/
88-(void)jlmediaPlayBack:(JLMediaPlayBack *)playback Status:(int)status;
89
90@end

2.4.4 双实时流显示

这个功能是提供给少数需要同时实现前后实时流同时显示的人员使用。

但当前的方法仅适用于 `低码流+使用udp传输实时流的设备`

需要实现的点如下:

  • JLMediaStreamManager类不能使用单例的,要使用实例化的

 1media_0 = [[JLMediaStreamManager alloc] init];
 2media_0.vPort = 6666;//视频流断开
 3media_0.aPort = 1234;//音频端口
 4media_0.sPort = 1423;//本地服务器端口
 5media_0.delegate = self;
 6
 7media_1 = [[JLMediaStreamManager alloc] init];
 8media_1.vPort = 7777;//视频流断开
 9media_1.aPort = 2234;//音频端口
10media_1.sPort = 1433;//本地服务器端口
11media_1.delegate = self;
  • 请求接收流和请求打开流

这里是需要先连接上接收流然后再打开流才会保障流的正确接收:

 1[self addNoralNote];//增加接收打开流的通知
 2   //只能使用UDP
 3 [media jlCreatePlayerWithType:type SetFps:sample_fps SetRate:sample_Rate SetFrame:CGSizeMake(widthInt, heightInt) StreamType:streamType];
 4
 5
 6if (type == MEDIA_FRONT) {
 7
 8    [[JLCtpSender sharedInstanced] dvRealTimeStreamFrontWebcamOpen:realType Width:widthInt Height:heightInt Fps:fpsInt Rate:8000];
 9
10}else if (type == MEDIA_POST){
11
12    [[JLCtpSender sharedInstanced] dvRealTimeStreamPullWebcamOpen:realType Width:widthInt Height:heightInt Fps:fpsInt Rate:8000];
13
14}
15-(void)addNoralNote{
16    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rtspIsBegin_0:) name:DV_OPEN_RT_STREAM object:nil];
17    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rtspIsBegin_1:) name:DV_OPEN_PULL_RT_STREAM object:nil];
18}
  • 打开实时流的时候不能同时打开,要一前一后这样打开才行,因为涉及到访问本地服务器的问题.

预览的播放器也要分别实例两个,而且实例化时访问的ip地址要注意对应上端口号 tcp://127.0.0.1:1423

播放器相关代码:

 1-(void)rtspIsBegin_0:(NSNotification *)note{
 2    IJKFFOptions *options = [IJKFFOptions optionsByDefault];
 3    [self ijkOptionSet:options];
 4
 5    if (ffmVC_0 == nil) {
 6    ffmVC_0 = [[IJKFFMoviePlayerController alloc] initWithContentURLString:@"tcp://127.0.0.1:1423" withOptions:options];
 7        [ffmVC_0 prepareToPlay];
 8    }else{
 9        [ffmVC_0 play];
10        NSLog(@"播放器没准备好");
11        return;
12    }
13
14    UIView *playerView = [ffmVC_0 view];
15    playerView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
16    playerView.frame = videoView0.bounds;
17    [videoView0 addSubview:playerView];
18    ffmVC_0.scalingMode = IJKMPMovieScalingModeAspectFit;
19
20}
21
22-(void)rtspIsBegin_1:(NSNotification *)note{
23    IJKFFOptions *options = [IJKFFOptions optionsByDefault];
24    [self ijkOptionSet:options];
25
26    if (ffmVC_1 == nil) {
27    ffmVC_1 = [[IJKFFMoviePlayerController alloc] initWithContentURLString:@"tcp://127.0.0.1:1433" withOptions:options];
28        [ffmVC_1 prepareToPlay];
29    }else{
30        [ffmVC_1 play];
31        NSLog(@"播放器没准备好");
32        return;
33    }
34
35    UIView *playerView = [ffmVC_1 view];
36    playerView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
37    playerView.frame = videoView0.bounds;
38    [videoView1 addSubview:playerView];
39    ffmVC_1.scalingMode = IJKMPMovieScalingModeAspectFit;
40}

2.5 无卡拍照/录影

本功能是基于进行实时流/回放能正常进行时,在手机端接受处理视频的数据流,进行封装。 目前所支持的有: #. AVI 格式的封装 #. MOV 格式的封装

具体的实例代码如下:

  1@interface NoCardRecordManager(){
  2
  3    BOOL rts_type;
  4    //码流
  5    NSInteger     rateInter;
  6    NSInteger     widthInter;
  7    JLFFCodecFrame  *ffDecode;
  8}
  9@end
 10
 11
 12@implementation NoCardRecordManager
 13@synthesize ffPaths;
 14
 15+(instancetype)sharedInstance{
 16    static NoCardRecordManager *vdm;
 17    static dispatch_once_t onceToken;
 18    dispatch_once(&onceToken, ^{
 19        vdm = [[NoCardRecordManager alloc] init];
 20    });
 21
 22    return vdm;
 23}
 24
 25-(instancetype)init{
 26
 27    self = [super init];
 28    if (self) {
 29        rts_type = NO;
 30        ffDecode = [[JLFFCodecFrame alloc] init];
 31    }
 32
 33    return self;
 34}
 35
 36-(void)startToNoCardRecordWithModel:(NSInteger)model{
 37    CGSize tmpSize = [self getSizeWithModel:model];
 38    [self startToNoCardRecordWithSize:tmpSize];
 39}
 40
 41-(CGSize)getSizeWithModel:(NSInteger)model{
 42    int height = 480;
 43    int width = 640;
 44
 45    switch (model) {
 46        case 1080:{
 47            height = 1080;
 48            width = 1920;
 49        }break;
 50
 51        case 720:{
 52            height = 720;
 53            width = 1280;
 54        }break;
 55
 56        case 480:{
 57            height = 480;
 58            width = 640;
 59        }break;
 60
 61        default:
 62            break;
 63    }
 64    return CGSizeMake(width, height);
 65}
 66
 67
 68-(void)startToNoCardRecordWithSize:(CGSize)size{
 69
 70
 71    NSDictionary *aDict = [AppInfoGeneral getDevComfigDictionary];
 72    NSDate *nowDate = [NSDate date];
 73    NSDateFormatter *dfm = [[NSDateFormatter alloc] init];
 74    [dfm setDateFormat:@"yyyyMMddHHmmss"];
 75    NSString *nowStr = [dfm stringFromDate:nowDate];
 76    NSString *uuidStr = [[JLCtpCaChe sharedInstance] devUUID];
 77    NSString *mypath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
 78    NSString *thePath = [NSString stringWithFormat:@"device_video/%@_%@.MOV",nowStr,uuidStr];
 79    mypath = [mypath stringByAppendingPathComponent:thePath];
 80    ffPaths = mypath;
 81    if ([aDict[@"rts_type"] isEqualToString:@"1"]) {
 82        [DFFile createPath:mypath];
 83    NSString *fps = [[JLCtpCaChe sharedInstance] videoMsgDict][@"fps"];
 84        NSDictionary *newDict = @{@"width":[NSString stringWithFormat:@"%.1f",size.width],@"height":[NSString stringWithFormat:@"%.1f",size.height],@"videoRate":fps};
 85        [[JLMovEncapsulator sharedInstanced] jlinitWithConfigurations:newDict];
 86        [[JLMovEncapsulator sharedInstanced] jlcreateWithPath:mypath];
 87
 88        rts_type = NO;
 89    }else{
 90        rts_type = YES;//jpeg
 91        JLAVIObject *aviObjc = [JLAVIObject new];
 92        aviObjc.avi_time = 0;
 93        aviObjc.avi_size = 0;
 94        aviObjc.avi_fps = [[[JLCtpCaChe sharedInstance] videoMsgDict][@"fps"] intValue];
 95        aviObjc.avi_Rate = [[[JLCtpCaChe sharedInstance] videoMsgDict][@"rate"] intValue];
 96        aviObjc.avi_width = size.width;
 97        aviObjc.avi_high = size.height;
 98        aviObjc.avi_Number = 16;
 99        aviObjc.avi_channels = 1;
100        aviObjc.avi_isVoice = YES;
101        NSString *theRPath = [mypath stringByReplacingOccurrencesOfString:@"MOV" withString:@"AVI"];
102        [[JLAVIEncapsulator sharedInstance] aviInitWithConfigurations:aviObjc WithPath:theRPath];
103    }
104
105    UIWindow *wd = [[UIApplication sharedApplication] keyWindow];
106    [DFUITools showText:kDV_TXT(@"开始录制") onView:wd delay:1.5];
107    [self addNote];
108
109}
110
111
112-(void)stopToNoCardRecord{
113    [self removeNote];
114
115    if (rts_type == YES) {
116        [[JLAVIEncapsulator sharedInstance] aviDidFinish];
117    }else{
118        [[JLMovEncapsulator sharedInstanced]jlClose];
119        [[ThumbnailClass sharedInstance] getVideosThumbnailWith:ffPaths];
120    }
121    UIWindow *wd = [[UIApplication sharedApplication] keyWindow];
122    [DFUITools showText:kDV_TXT(@"录制完毕") onView:wd delay:1.5];
123}
124
125-(void)getIFrameAsPhotoModel:(NSInteger)model{
126    CGSize tmpSize = [self getSizeWithModel:model];
127    [self getIFrameAsPhoto:tmpSize];
128}
129
130-(void)getIFrameAsPhoto:(CGSize )size{
131    rateInter = size.height;
132    widthInter = size.width;
133    [DFNotice add:DV_VIDEO_DATA_STREAM Action:@selector(noTFPhotoDataCallBlack:) Own:self];
134}
135
136
137-(void)addNote{
138    [DFNotice add:DV_PCM_DATA Action:@selector(noTFCardPcmDataCallBlack:) Own:self];
139    [DFNotice add:DV_VIDEO_DATA_STREAM Action:@selector(noTFCardVideoDataCallBlack:) Own:self];
140}
141-(void)removeNote{
142    [DFNotice remove:DV_PCM_DATA Own:self];
143    [DFNotice remove:DV_VIDEO_DATA_STREAM Own:self];
144}
145
146#pragma mark <- 保存无卡的pcm数据
147- (void)noTFCardPcmDataCallBlack:(NSNotification *)note{
148
149    NSDictionary *dict = [note object];
150    NSData *data = dict[@"data"];
151    if (rts_type == YES) {
152        [[JLAVIEncapsulator sharedInstance] aviWirteDataToEncapsulator:data WithType:0];
153    }else{
154        [[JLMovEncapsulator sharedInstanced] jlWirteDataToEncapsulator:data WithType:0];
155    }
156
157}
158
159#pragma mark <- 保存无卡的h264/jpg数据
160- (void)noTFCardVideoDataCallBlack:(NSNotification *)note{
161    NSDictionary *dict = [note object];
162    NSData *data = dict[@"data"];
163
164    if (rts_type == YES) {
165        [[JLAVIEncapsulator sharedInstance] aviWirteDataToEncapsulator:data WithType:1];
166    }else{
167        [[JLMovEncapsulator sharedInstanced] jlWirteDataToEncapsulator:data WithType:1];
168    }
169
170}
171
172
173
174#pragma mark <-- 保存无卡的拍照的数据
175-(void)noTFPhotoDataCallBlack:(NSNotification *)note{
176    NSDictionary *dict = [note object];
177    NSData *data = dict[@"data"];
178    if((int)[[JLCtpCaChe sharedInstance] videoStreamType]==0){
179        [DFNotice remove:DV_VIDEO_DATA_STREAM Own:self];
180        NSDate *nowDate = [NSDate date];
181        NSDateFormatter *dfm = [[NSDateFormatter alloc] init];
182        [dfm setDateFormat:@"yyyyMMddHHmmss"];
183        NSString *nowStr = [dfm stringFromDate:nowDate];
184        NSString *uuidStr = [[JLCtpCaChe sharedInstance] devUUID];
185        NSString *nameStr = [NSString stringWithFormat:@"%@_%@_%@",nowStr,uuidStr,[NSString stringWithFormat:@"JPEG_NO_TF_IMG.JPG"]];
186
187        NSString *basePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
188        basePath = [basePath stringByAppendingPathComponent:@"device_image"];
189        basePath = [basePath stringByAppendingPathComponent:nameStr];
190
191        [DFFile createPath:basePath];
192        [DFFile writeData:data fillFile:basePath];
193    }else{
194        NSData *headData = [data subdataWithRange:NSMakeRange(4, 1)];
195        NSInteger headInteger = [self dataToInt:headData];
196
197        if(headInteger==103){
198            [DFNotice remove:DV_VIDEO_DATA_STREAM Own:self];
199            NSDictionary *dict = @{@"height":[NSString stringWithFormat:@"%ld",(long)rateInter],@"width":[NSString stringWithFormat:@"%ld",(long)widthInter],@"name":[NSString stringWithFormat:@"H264_NO_TF_IMG.JPG"]};
200            //异步执行,避免UI线程卡死
201            [DFAction mainTask:^{
202                [self->ffDecode jlFFonFrameCodeWith:data withDict:dict];
203            }];
204
205        }
206    }
207}
208
209
210//把data(十六进制)转化为为十进制整型
211-(NSInteger)dataToInt:(NSData *)data{
212
213    if (data.length == 1) {
214        int k;
215        [data getBytes: &k length: sizeof(k)];
216        return k;
217    }
218    if (data.length == 2) {
219        return CFSwapInt16BigToHost(*(int*)([data bytes]));
220    }
221    if (data.length == 4) {
222        return CFSwapInt32BigToHost(*(int*)([data bytes]));
223    }
224    return CFSwapInt16BigToHost(*(int*)([data bytes]));
225
226}
227
228
229
230@end

2.6 文件浏览

手机端对设备先进行设备文件列表请求:

 1//JLCtpSender.h
 2
 3#pragma mark <- 文件浏览及文件操作相关 ->
 4/**
 5进入/退出浏览模式
 6
 7@param Index 状态(0:进入 1:退出)
 8*/
 9- (void)dvInOrExitBrowsMode:(int)Index;
10
11
12/**
13媒体文件列表获取
14
15@param index (0:前视 1:后视)
16@param type (0:空 1:全部 2:视频 3:图片)
17*/
18-(void)dvGetDeviceMediaListWith:(int)index type:(int)type;

在收到文件列表的Notification回调 DV_BEHIND_MEDIA_FILES_LIST 之后,需要去下载完整的列表数据,如下:

 1[DFNotice add:DV_BEHIND_MEDIA_FILES_LIST Action:@selector(postMediaFilesList:) Own:self];
 2-(void)downloadVf_listTxt:(NSString *)fileName{
 3    NSString *baseurl = [NSString stringWithFormat:@"http://%@:8080/%@",DV_TCP_ADDRESS,fileName];
 4    NSURL *url = [NSURL URLWithString:baseurl];
 5    NSURLRequest *request = [NSURLRequest requestWithURL:url];
 6
 7    NSURLSessionDataTask *downloadTask = [afmanager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
 8        if (error) {
 9            NSLog(@"error:%d",error.code);
10        }else{
11            NSData *data = responseObject;
12            NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
13            NSString *targetFile = [fileName lastPathComponent];
14            path = [path stringByAppendingPathComponent:targetFile];
15            [DFFile writeData:data fillFile:path];
16            NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
17
18            dict = [AppInfoGeneral vflistReSorting:dict];
19            NSArray *baseArray = dict[@"file_list"];
20            NSMutableArray *tmpArray = [[NSMutableArray alloc] initWithArray:baseArray];
21            [AppInfoGeneral quickSortFromLeft:0 toRight:tmpArray.count-1 WithArray:tmpArray];
22            NSDictionary *newDict = @{@"file_list":tmpArray};
23            NSData *newData = [NSJSONSerialization dataWithJSONObject:newDict options:NSJSONWritingPrettyPrinted error:nil];
24            [DFFile writeData:newData fillFile:path];
25            [[NSNotificationCenter defaultCenter] postNotificationName:@"VF_LIST_TXT" object:dict];
26        }
27    }];
28    [downloadTask resume];
29
30}

由于设备的文件都只是录制好的视频文件或是照片之类的文件,是不可直接浏览的,需要配合上文提到的 视频缩略图模块 进行一并展示。

2.6.1 获取设备图片

先需要通过命令FORWARD_MEDIA_FILES_LIST、BEHIND_MEDIA_FILES_LIST 获取设备中所有的文件(视频文件、音频文件)的描述文件vf_list.txt,然后通过http下载该文件,就可得到设备中的文件列表内容。 参考流程图如下:

iPhone->Device:get vf_list.txt
Note left of iPhone:ex:[[JLCtpSender sharedInstanced] dvGetDeviceMediaListWith:0 type:1];
Device->iPhone:Device's vf_list.txt is ready
Note right of Device:DV_FORWARD_MEDIA_FILES_LIST(列表)、DV_BEHIND_MEDIA_FILES_LIST(后视列表)
iPhone->Device:Download vf_list.txt
Note left of iPhone:"http://%@:%d/%@",DV_TCP_ADDRESS,DV_HTTP_PORT,fileName

2.7 文件下载

首先是要从设备获得完整的文件列表,可参考上文提到的 文件浏览 ,先获取到了设备文件列表。

视频/音频文件下载包括了两种方式: #. 通过回放时下载,参考组合回放时处理视频流,把它封装成视频文件 #. 通过HTTP下载

2.7.1 视频下载流程

在回放模式下,会有三个通知回调分别是:音频 DV_PCM_DATA_B 以及视频 DV_VIDEO_DATA_B 以及时间返回: DV_DATE_TIME_B`(可在 `JLDV16SDK.h 类中找到相关信息)。

1、回放时边下边播

在对应要下载的开始时间点,例如某个文件的偏移10000ms,然后通过 JLCtpSender.h`类 的 `dvTimeAxisPlay:(NSDictionary *)dict 开始回放操作 ,打开 JLMediaPlayBlack.h 类的 jlMediaPlayBlackStart 方法打开数据接收器(这里其实是和回放的操作是一致的),然后打开mov封装器,把接收到的音频数据和视频数据都放到里面去,然后在适当的时候,关闭封装器,那就完成了下载封装。

2、两个时间点之间下载

对应要下载的开始时间点,通过 JLCtpSender.h 类 的 dvTimeAxisPlay:(NSDictionary *)dict 方法进行回放操作,打开 JLMediaPlayBlack.h 类的 jlMediaPlayBlackStart 方法打开数据接收器(这里其实是和回放的操作是一致的),利用时间戳 DV_DATE_TIME_B 计时,当时间满足两个时间点只差,即可停止播放,发送关闭回放的命令 dvTimeAxisPlayControl:(int)status 和停止接收的命令 jlMediaPlayBlackOver , 然后再关闭封装器,即可完成下载。 具体可参考demo中的 MediaDownloadAction.h

2.8 固件升级

2.8.1 双备份升级

  1. 向小机请求固件版本 <http://192.168.1.1:8080/mnt/spiflash/res/dev_desc.txt>

  2. APP获取服务器端的版本信息

  3. 进行版本校对和下载

  4. 下载服务器上的固件文件

  5. 把升级文件通过ftp传输到小机

  6. 发命令让小机重启

重要

注:手机和固件的大文件传输使用的是FTP传输。

请求步骤:

1、请求固件的版本信息:

 1NSString *urlPath = [NSString stringWithFormat:@"http://%@:8080/mnt/spiflash/res/dev_desc.txt",DV_TCP_ADDRESS];
 2NSURL *url = [NSURL URLWithString:urlPath];
 3NSURLRequest *request = [NSURLRequest requestWithURL:url];
 4
 5NSURLSessionDataTask *downloadTask = [afmanager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
 6    if (error) {
 7        NSLog(@"error:%d",error.code);
 8    }else{
 9        NSData *data = responseObject;
10        NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
11        path = [path stringByAppendingPathComponent:@"/DVConfig/dev_desc.txt"];
12        [DFFile createOn:NSLibraryDirectory MiddlePath:@"DVConfig" File:@"dev_desc.txt"];
13        [DFFile writeData:data fillFile:path];
14        NSString *p_path = [[NSBundle mainBundle] pathForResource:@"product_type" ofType:@"plist"];
15        NSArray *productArray = [NSArray arrayWithContentsOfFile:p_path];
16        NSDictionary *devDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
17        NSLog(@"dev_desc.txt download Succeed");
18        //判断获得了设备的固件版本
19        NSString *version = devDict[@"firmware_version"];
20                                            NSLog(@"firmware Version:%@",version);
21
22        }
23    }];
24    [downloadTask resume];
25}

2、APP获取服务器端的版本信息(因为我们没有配备通用服务器,所以这里需要贵司另外订制,此处不作详述)

3、下载服务器上的固件文件(因为我们没有配备通用服务器,所以这里需要贵司另外订制,此处不作详述)

4、发起上传固件的请求主要是使用了FTP,(设备是遵循FTP协议的,如果觉得当前的库不满足或者存在问题,可以另外使用别的开源库来完成这一步骤。

 1-(void)updateLoadFile:(NSString *)path{
 2
 3    uploadData = [NSData dataWithContentsOfFile:path];
 4    uploadFile = [[BRRequestUpload alloc] initWithDelegate:self];
 5
 6    NSString *fileLenth = [NSString stringWithFormat:@"%lld",[self fileSizeAtPath:path]];
 7    NSLog(@"---fileLenth---%@",fileLenth);
 8    NSString *toPath = [NSString stringWithFormat:@"%s %@","/JL_AC54.bfu",fileLenth];
 9    NSLog(@"---toPath---%@",toPath);
10
11    uploadFile.path = toPath;
12    uploadFile.hostname = @"192.168.1.1";
13    uploadFile.username = @"FTPX";
14    uploadFile.password = @"12345678";
15    [uploadFile start];
16}

5、接收到固件上传成功通知

1[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(uploadOver:) name:@"FTP_UPLOAD_OK" object:nil];

6、发送让命令让固件重启即可

//重启小机
[[JLCtpSender sharedInstanced] dvMakeDeviceReset];

2.8.2 单备份升级

  1. 步骤说明

  • 描述文档多加”ota_type”参数和”ota_step”参数

    • ota_type值为0或者没有ota_type则为双备份

    • ota_type值为1为单备份:”ota_type”: “1”

    • ota_step值为0表示:单备份升级,需要先执行第一阶段

    • ota_step值为1为单备份正在升级(此时需要进入第二阶段升级):”ota_step”: “1”

    • 有ota_type则必有ota_step

    • “ota_type” 0 无, 1单备份

    • “ota_step” 0 可进行第一阶段的单备份升级, 1单备份正在升级第二阶段

  • CTP命令

    加一条命令:”NET_OTA_SINGLE” , 只有APP向设备请求,参数param”:{“status”:”1”}}(已包含到 JLOtaSingleWay 中,外边可选择监听) 返回参数的status参数,status:1 则设备已准备好单备份升级 返回参数的status参数,status:0 则设备单备份升级失败

  1. 升级流程

  • 第一阶段升级:

(1)APP连入获取ota_type和ota_step,ota_type值为1,ota_step = 0,则可以进行单备份升级第一阶段
(2)进入设备单备份升级,发送CTP命令:NET_OTA_SINGLE,参数:param”:{“status”:”1”}}
(3)APP收到设备的”NET_OTA_SINGLE”回应NOTIFY后,status=1,则设备成功进入单备份升级模式
(4)连接3335端口TCP,进行单备份升级,此后进入接收recv等待接收数据和读取文件
(5)设备升级完成或升级失败: socket收到数据头的status=NET_UPDATE_STATUS_ERR,或者接收超时一定次数,升级失败。 socket收到数据头的status=NET_UPDATE_STATUS_OK,升级成功。
(6)升级成功后,设备重启,APP要手动连接设备热点
(7)如果APP主动断开则,需要往3335的socket发送NET_UPDATE_STATUS_STOP状态
  • 第二阶段升级:

(1)APP连入3333的CTP端口,发送接入设备的ACCEPT,设备回应APP_ACCESS,带参数{“op”:”NOTIFY”,”param”:{“type”:”0”,”ver”:”20906”}},即可进入下一步
(2)APP连入http获取ota_type和ota_step,ota_type值为1,ota_step = 1,则可以进行单备份升级第二阶段
(3)连接3335端口TCP,进行单备份升级,此后进入接收recv等待接收数据和读取文件
(4)设备升级完成或升级失败: socket收到数据头的status=NET_UPDATE_STATUS_ERR,或者接收超时一定次数,升级失败。 socket收到数据头的status=NET_UPDATE_STATUS_OK,升级成功。 socket收到数据头的status=NET_UPDATE_STATUS_SAME,升级的是同一个固件。
(5)升级成功,设备重启
(6)如果APP主动断开则,需要往3335的socket发送NET_UPDATE_STATUS_STOP状态
  • API接口:

 1typedef NS_ENUM(uint8_t, JLOTAStatus) {
 2    ///请求总包信息
 3    JLOTAStatusInfo         = 0 << 0,
 4    ///传输完成
 5    JLOTAStatusOK           = 1 << 0,
 6    ///升级错误
 7    JLOTAStatusErr          = 1 << 1,
 8    ///传输中
 9    JLOTAStatusTransport    = 1 << 2,
10    ///停止状态
11    JLOTAStatusStop         = 1 << 3,
12    ///同一个文件
13    JLOTAStatusSameFile     = 1 << 4,
14};
15
16
17
18
19@protocol JLOTAProtocol <NSObject>
20
21-(void)updateStatus:(JLOTAStatus)status Progress:(float)progress Error:(NSError* _Nullable)err;
22
23@end
24
25@interface JLOTASingleWay : NSObject
26
27@property(nonatomic,strong,readonly)NSData *bfuData;
28@property(nonatomic,weak)id<JLOTAProtocol> delegate;
29
30/// 开始OTA单备份升级
31/// @param path 升级文件存放路径
32/// @param step 升级阶段,从描述文件中获取,字段为“ota_step”
33-(void)startOta:(NSString *)path withStep:(NSInteger)step;
34
35/// 中断OTA单备份升级
36/// @param stopBlock stopBlock description
37-(void)stopOta:(void(^)(BOOL status))stopBlock;
38
39/// 取消OTA升级
40-(void)cancelOta;
41
42/// 销毁对象
43-(void)destroy;
  • 使用Demo

 1@interface OtaSingle ()<JLOTAProtocol>{
 2    JLOTASingleWay *otaUpdate;
 3}
 4@end
 5
 6@implementation OtaSingle
 7
 8-(void)init{
 9    otaUpdate = [[JLOTASingleWay alloc] init];
10    otaUpdate.delegate = self;
11}
12/// start ota
13/// @param path .ufw save path
14/// @param step witch step
15-(void)start:(NSString *)path Step:(NSInteger)step{
16    //
17    [otaUpdate startOta:path withStep:step];
18}
19
20//MARK: - update callback
21-(void)updateStatus:(JLOTAStatus)status Progress:(float)progress Error:(NSError * _Nullable)err{
22    //TODO: witch status should do
23}
24
25@end

2.9 自定义命令

在实际使用中会遇到不确定的业务,这时候需要自定义一些命令来实现这些业务,实现的步骤可以参考以下:

自定义命令格式如下所示:

 1 {
 2  "op":"PUT", //操作类型,value0包括“PUT","GET","NOTIFY"
 3    "param":{// value1-n皆对应相应参数,若无可省略相关字段
 4    "key_0":"value1",
 5    "key_1":"value2",
 6    "key_2":"value3",
 7    "key_3":"value4",
 8    ......
 9    }
10}

op为操作类型,当手机”PUT”或”GET”命令时,设备统一回复”NOTIFY”。

示例: Topic: “SD_STATUS”

1 {
2    "errno":0,
3    "op":"NOTIFY",
4    "prarm":{
5            "key_0":"1",        //在线状态(0:离线, 1:在线)
6    }    //操作类型("GET","PUT")
7}

自定义发送(接收)命令例子

 1/**发送命令
 2这是一个topic名字为:TEST_TOPIC的测试例子
 3对应字典内容为
 4{
 5    "op":"PUT", //操作类型,value0包括“PUT","GET"
 6        "param":{// value1-n皆对应相应参数,若无可省略相关字段
 7        "key_0":"value0",
 8        "key_1":"valu1",
 9        ......
10        }
11    }
12*/
13NSDictionary *dict = @{@"op":@"PUT",@"param":@{@"key_0":@"value0",@"key_1":@"value1"}};
14[[JLCtpSender sharedInstanced] dvGenericCommandWith:dict Topic:@"TEST_TOPIC"];
15
16/*
17//接收命令
18在需要接收的页面添加上对应的通知接收
19对应字典内容为
20{
21        "errno":0,
22        "op":"NOTIFY",
23        "prarm":{
24            "key_0":"1",        //在线状态(0:离线, 1:在线)
25        }    //操作类型("GET","PUT")
26
27
28}
29*/
30[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(testAction:) name:@"TEST_TOPIC" object:nil];
31
32-(void)testAction:(NSNotification *)note{
33
34    NSDictionary *dict = [note object];
35    if ([dict[@"op"] isEqualToString:@"NOTIFY"]) {
36        NSDictionary *myDic = dict[@"param"];
37
38            }
39    }
40}