加载中
加载中
表情图片
评为精选
鼓励
加载中...
分享
加载中...
文件下载
加载中...
修改排序
加载中...
写了一个用于Sony相机的定位安卓App
m24h2025/05/22原创 软件综合 IP:上海

开源在 https://github.com/m24h/sonycamloc

可安装的Apk在https://github.com/m24h/sonycamloc/blob/master/code/app/release/app-release.apk


以前我用A7R4的时候 用Sony IME就发现它的定位根本不好用 连上一会就断 反编译发现是手机端认为位置没变 就不需要告诉相机了 但相机过了一阵就会让位置信息失效 也不知道索尼家的两边工程师怎么沟通的 我只好反编译修改再编译 才勉强能用(可以翻5年前我的另一篇文章)

现在换A7CR了 IME不能用得改用Creator's App 问题接着延续 不想继续hack了 干脆自己写一个吧

用索尼微单的可以试试 但我只有一台A7CR 手机端要求最低Android 13 我也只有一台小米 兼容性是没法保证的

非索尼用户的话 我这个App结构简单 有蓝牙和定位操作 有典型的运行时权限申请 有个非常短小精悍的ListView的databinding类 可以借鉴吧

剩下的是吐槽一下蓝牙GATT的service discovery过程 之前我没有了解过 但是现在我想“天啊 这么广泛使用的东西 居然有夹杂这么有损智商的协议”

这种情况有点像DNS过程 GATT的服务和特征都是UUID表示的 很长 实际访问需要先翻译成个16位的句柄字 才能作为目的地址 毕竟MTU初始也就23个字节 如果放UUID 就什么内容也别想传了

我以为会有种方法 可以像DNS那样去问 没想到的过程是这样的 从开始问到65535 是这个吗 还是这个 或者是这个。。。天啊 是河神设计出来的协议吗

然后又得吐槽索尼了 一般人家也不会在蓝牙里面塞那么多服务和特征 所以问两下也问完了 但是索尼却塞了近200条 我需要的特征句柄号排到了240多 真不知道它的蓝牙能干多少事

Screenshot_2025-05-22-13-12-17-125_cn.wch.bledemo.jpg

正所谓卧龙遇到凤雏 因此每次连上发现服务都得花几秒钟的时间 不然没法使用

我想 这也罢了 第一次可以忍耐 那么我可以缓存吧 以后就直接用句柄号吧 这时候 周都督也来了

就是Android的BLE框架 和Windows不同 它封装了并且拒绝你去知道具体的句柄号 仿佛怕你直接用ATT协议甚至L2CAP 于是 这三家凑一起 每次连相机都逃不掉等个几秒 幸好的是相机关机前倒不用再问了

我写的这个程序是中规中矩的 但有时候还会碰到问题 如果连上相机10秒没出现定位符号 应该是报“堵塞”错误了 可以重新开关相机 不行就开关蓝牙清除缓存之类 要么退出程序重新进入 虽然是前台服务 毕竟没有进厂商白名单 有不知道什么时候失活的可能 所以即使是开发者 我也没找到真正问题的所在 可能就这样用着吧

我也不知道如果自己提供个Handler 使用自己的新线程去维护蓝牙操作 会比用系统默认提供的更好还是更差 会不会反而更容易被杀掉

毕竟是第一个Android程序 经验不足

其实满心槽点 比如BLE的回调缺乏用户自定义对象 虽然安全但是不好用 写个符合数据一致性的程序太难只能凑合 甚至索尼A7CR奇怪的操作逻辑 算了 都是草台班子


[修改于 2个月7天前 - 2025/05/22 14:37:04]

来自:计算机科学 / 软件综合
2
5
新版本公告
~~空空如也
m24h 作者
1个月28天前 IP:上海
944482

这个软件现在遇到最大的问题 就是慢 几乎每次连上相机都需要接近10秒的时间 让人不耐

经查 主要消耗时间在ble的discoverServices()上 因为我确定 其实并不需要每次都discoverServices() 而看下面底层调用Gatt service的源码 证实保存的BluetoothGattCharacteristic是可用再用的 因为只需要用的是其getInstanceId()得到的Gatt Handle号 这个其实对本应用是不变的

Java
public int writeCharacteristic( @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value, @WriteType int writeType) { ..... try { for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) { requestStatus = mService.writeCharacteristic( mClientIf, device.getAddress(), characteristic.getInstanceId(), writeType, AUTHENTICATION_NONE, value, mAttributionSource);

于是 我决定再次连上 如果需要的Characteristic还在 就不进行discoverServices()了

Kotlin
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { ...... if (faithMode && characteristicGpsData!=null) { gattConnected=true cameraInitialized=false lastSyncTime=null loopActor.trySend(Unit) } else { gatt.discoverServices() }

然而并没有尽全用 有时快有时慢

抓蓝牙包发现 即使我没有明式地调用discoverServices() 它仍然可能发生 有趣的是 即使第一次连接 需要明式地调用discoverServices() 它反而有可能不发生 显然后台有一个不可控的cache之类的东西

只能看AOSP源代码了 看看连接后发生了什么 底层是怎么搞cache的

C++
void bta_gattc_conn(tBTA_GATTC_CLCB* p_clcb, const tBTA_GATTC_DATA* p_data) { ...... /* start database cache if needed */ if (p_clcb->p_srcb->gatt_database.IsEmpty() || p_clcb->p_srcb->state != BTA_GATTC_SERV_IDLE) { if (p_clcb->p_srcb->state == BTA_GATTC_SERV_IDLE) { p_clcb->p_srcb->state = BTA_GATTC_SERV_LOAD; // Consider the case that if GATT Server is changed, but no service // changed indication is received, the database might be out of date. So // if robust caching is known to be supported, always check the db hash // first, before loading the stored database. // Only load the database if we are bonded, since the device cache is // meaningless otherwise (as we need to do rediscovery regardless) gatt::Database db = btm_sec_is_a_bonded_dev(p_clcb->bda) ? bta_gattc_cache_load(p_clcb->p_srcb->server_bda) : gatt::Database(); auto robust_caching_support = GetRobustCachingSupport(p_clcb, db); log::info("Connected to {}, robust caching support is {}", p_clcb->bda.ToRedactedStringForLogging(), robust_caching_support); if (!db.IsEmpty()) { p_clcb->p_srcb->gatt_database = db; } if (db.IsEmpty() || robust_caching_support != RobustCachingSupport::UNSUPPORTED) { // If the peer device is expected to support robust caching, or if we // don't know its services yet, then we should do discovery (which may // short-circuit through a hash match, but might also do the full // discovery). p_clcb->p_srcb->state = BTA_GATTC_SERV_DISC; /* set true to read database hash before service discovery */ p_clcb->p_srcb->srvc_hdl_db_hash = true; /* cache load failure, start discovery */ bta_gattc_start_discover(p_clcb, NULL); } else { p_clcb->p_srcb->state = BTA_GATTC_SERV_IDLE; bta_gattc_reset_discover_st(p_clcb->p_srcb, GATT_SUCCESS); } } else { /* cache is building */ p_clcb->state = BTA_GATTC_DISCOVER_ST; } } else { /* a pending service handle change indication */ if (p_clcb->p_srcb->srvc_hdl_chg) { p_clcb->p_srcb->srvc_hdl_chg = false; /* set true to read database hash before service discovery */ p_clcb->p_srcb->srvc_hdl_db_hash = true; /* start discovery */ bta_gattc_sm_execute(p_clcb, BTA_GATTC_INT_DISCOVER_EVT, NULL); } }

这里我要blame一下这个cache的思想了

如果高层程序员需要cache 自然会自己写 如果底层有cache 则应该提供一些控制接口 诸如允许或者不允许 现在就是政令不通 底层有自己的想法 队伍不好带了的感觉 需要discoverServices()的时候 它可能过于自信 未必真去刷新 不需要的似乎它反而去刷新 (甚至没有等待connection update过程结束 导致了Different Transaction Collision)


设备如果是刚开始连接 反而会有可能使用缓存的数据

C++
if (p_clcb->p_srcb->gatt_database.IsEmpty() ...) { ..... gatt::Database db = btm_sec_is_a_bonded_dev(p_clcb->bda) ? bta_gattc_cache_load(p_clcb->p_srcb->server_bda) : gatt::Database();

这也是为什么我第一次连接 需要discoverServices()的时候 经常没有实际发生 速度很快

但是之后HAL的db不空(因为我为了及时发现相机连接 使用了自动连接 状态是保留且持续的) 再次连接就需要看有没有发生service changed 如果发生了 会再次start discovery

C++
else { /* a pending service handle change indication */ if (p_clcb->p_srcb->srvc_hdl_chg) { p_clcb->p_srcb->srvc_hdl_chg = false; /* set true to read database hash before service discovery */ p_clcb->p_srcb->srvc_hdl_db_hash = true; /* start discovery */ bta_gattc_sm_execute(p_clcb, BTA_GATTC_INT_DISCOVER_EVT, NULL); }

令人遗憾的是 Sony相机是个保守主义者 它有事没事都经常会发service changed 大抵是认出这个连接者来过 并执意让它不使用缓存

上面的流程 除非改写并编译自己的手机固件 没有什么改变的余地 从上往下撕个口子一下真的很难 我没有找到什么方法透过service/JNI直达底层 但我并没有放弃 还是看看这个蛋有没有条缝可用让我叮一下的

C++
/** Start a discovery on server */ void bta_gattc_start_discover(tBTA_GATTC_CLCB* p_clcb, const tBTA_GATTC_DATA* /* p_data */) { .... /* set all srcb related clcb into discovery ST */ bta_gattc_set_discover_st(p_clcb->p_srcb); ..... /* clear the service change mask */ p_clcb->p_srcb->srvc_hdl_chg = false; p_clcb->p_srcb->update_count = 0; .....

可以看到 discovery并不是一个立即执行的过程 只是往状态机里设置了将要进入的状态 不过话又说回来 即使立即发出了查询包 也不影响我叮这颗蛋的姿势

我的做法很简单 设置一个2秒的超时 一般写蓝牙Characteristic不会花那么多时间 如果堵塞了 说明正在等待discovery结束 那么 我就disconnect 再马上connect 因为实际上 虽然discovery没成功完成 但是srvc_hdl_chg已经先被设为false了!!!! 这就是关键!!!!

Kotlin
private suspend fun gattWrite(characteristic: BluetoothGattCharacteristic, bytes: ByteArray) : Boolean? { return withTimeoutOrNull (if (faithMode) 2000L else 20000L) { while (gattStatus!=null) delay(100) suspendCancellableCoroutine<Boolean> { cont -> cont.invokeOnCancellation { gattStatus=null Log.e("MainService.gattWrite", "timeout, try to reconnect") // this will break discovery progress in backend for faith mode, and speed up the response gatt?.disconnect() gatt?.connect() } gattStatus=cont .....

实测有效 discovery进程被打断 虽然重连了一下 但是时间由原来的9秒变成了4秒 对于即时拍摄就容易接受了

如果在onConnectionStateChange()的时候 有手段知道底层会不会discovery 立即重连 这个时间有望减到2秒

如果新的android底层发现这个事务型漏洞并弥补上 我的程序可能会陷入不断re-connect 但是这也是我为什么专门设置一个可以开关的faith mode的原因 加速源于hack 算是真正的"黑科技了" 但是我想 至少到Android15这个服方法都奏效 而且Android开发人员不会来看这个贴子(那我得喷他一脸 一个不可控得cache是为了啥) 编程中也不可能什么都能以安全事务的形态进行 那程序会上升到不可思议的难度


引用
评论
2
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
m24h作者
27天6时前 IP:上海
945211

新框架 抽象了相机接口(目前支持索尼和理光GR3 可能适用宾得相机) 支持多种相机和多个相机同时定位 增加了更慢的速度来保证定位精度和防止漂移

IMG_20250701_104205.jpg


引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
m24h
进士 学者 机友
文章
59
回复
938
学术分
1
2020/01/22注册,2天6时前活动

个人开源项目: XXXXXXXXXXXXXX

主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:上海
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的