This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
1. Product vision
A minimal Android app (Kotlin) that can, on user demand, capture video + audio in the background, stream it live through WebRTC to a backend, and locally buffer a loss‑tolerant copy. On the server side we hash each media chunk and immediately obtain a qualified timestamp (QTSP) or blockchain attestation, preserving an end‑to‑end, court‑ready chain of custody.
2. Functional requirements
ID | Requirement | Notes |
---|---|---|
FR‑1 | User can start/stop a secure recording session from a single UI toggle. | Must show foreground notification due to Android 13+ background recording rules. |
FR‑2 | App records front or rear camera and microphone simultaneously. | 1080 p @ 30 fps baseline. |
FR‑3 | App survives screen‑off / app switch (uses Foreground Service). | "MediaProjection + CameraX" or pure CameraX route. |
FR‑4 | Each encoded frame is sent via WebRTC data/media channel to server. | SRTP for privacy. |
FR‑5 | If network is lost, chunks are stored encrypted in app sandbox and retried with WorkManager. | |
FR‑6 | Server receives RTP, hashes every GOP with SHA‑256, requests timestamp, stores tuple (hash, RFC 3161 TST, chunk path) in DB + S3‑like object store. | |
FR‑7 | App exposes a "Verify" screen: download TST receipt & proof for any session, recompute local hash, and show green / red indicator. |
3. High‑level architecture
┌───────────────┐ SRTP / ICE ┌──────────────────┐
│ Android App │ ─────── peer connection ─► │ Signalling + │
│ Kotlin + │ ◄────── (TURN fallback) ◄─ │ Media SFU/Router│
│ Foreground │ │ (Janus / Pion) │
│ Service │ REST/WebSocket │ │
└──────▲────────┘ │ └─────────▲────────┘
│ local AES‑256 store │ SHA‑256(chunk) │ gRPC
│ (Room + SQLCipher) ▼ ▼
┌──────┴────────┐ ┌───────────────┐ ┌───────────────┐
│ WorkManager │ │ Hasher + │ TSP │ Qualified │
│ retry queue │ │ TimeStamper │◄────►│ TS Authority │
└───────────────┘ └───────────────┘ └───────────────┘
- Media path: CameraX → MediaCodec (H.264) →
SurfaceTexture
→ WebRTC VideoTrack. - Audio path:
AudioRecord
(OPUS) → WebRTC AudioTrack.
4. Android implementation details
4.1 Permissions & privacy
CAMERA
,RECORD_AUDIO
,FOREGROUND_SERVICE_CAMERA
,POST_NOTIFICATIONS
(Tiramisu+).- Runtime request flow wrapped in coroutine
suspend fun requestPermissions()
.
4.2 Foreground RecordingService
@ForegroundService
class RecordingService : Service() {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
override fun onStartCommand(i: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIF_ID, buildNotif())
scope.launch { startCaptureSession() }
return START_STICKY
}
private suspend fun startCaptureSession() {
val camera = CameraXInitializer(cameraSelector).open()
val rtcClient = WebRtcClient(iceServers)
val videoSrc = CameraXVideoSource(camera)
rtcClient.addTrack(videoSrc.track)
rtcClient.addTrack(MicAudioSource().track)
rtcClient.connect(signalServer)
}
override fun onDestroy() {
scope.cancel();
super.onDestroy()
}
}
Notification must be user‑dismissible and display recording timer — meets Android policy.
4.3 Local buffering & WorkManager
class ChunkWriter(private val ctx: Context){
suspend fun saveChunk(bytes: ByteArray){
val encrypted = crypto.encrypt(bytes)
room.chunkDao().insert(ChunkEntity(ts=now(), blob=encrypted))
}
}
PeriodicWorkRequest
checks DB → upload to server via REST → on success delete row.
5. Server‑side pipeline (Golang example)
// Media handler (Pion SFU hook)
func onRTPPacket(pkt *rtp.Packet){
chunkBuf.Write(pkt.Payload)
if gopFinished(pkt){
h := sha256.Sum256(chunkBuf.Bytes())
tst, _ := tspClient.Timestamp(h[:])
store.Save(chunkBuf.Bytes(), h[:], tst)
chunkBuf.Reset()
}
}
Timestamp sources:
- QTSP (eIDAS) – Camerfirma TSA, FNMT, Poste Italiane, etc.
- Blockchain fallback – embed hash in Bitcoin OP_RETURN; use public API for proof.
6. Performance & energy
Component | Trade‑off | Recommendation |
---|---|---|
CameraX vs. MediaRecorder | CameraX gives Surface for WebRTC; slightly higher CPU | Acceptable on devices ≥ Snapdragon 730 |
Video codec | H.264 Baseline profile | Widely HW‑accelerated, 1 Mbps @1080p 30fps ≈ 2 h ≈ 1 GB |
Audio | OPUS 48 kHz 16 kbps | Low bandwidth, good quality |
Encryption | SRTP inline; local AES‑256 | negligible on ARMv8 AES‑NI |
7. Security considerations
- Zero‑Config Keys: generate on first run, stored in Android Keystore.
- Pinning: TLS cert pin to signalling + REST host.
- Tamper‑evident log: every state change appended to SQLCipher DB with incremental hash chain.
8. Roadmap
- P0 – Local capture + save MP4.
- P1 – Add WebRTC streaming via public TURN.
- P2 – Server hash + QTSP integration.
- P3 – Verification UI + export proof bundle (ZIP: video + JSON hash + TST).
- P4 – Battery optimisation, Doze‑mode job scheduling.
9. References / Tooling
- CameraX Jetpack lib.
- WebRTC‑KMP for multiplatform client.
- Pion SFU (Go) or Janus (C) backend.
- RFC 3161 Time‑Stamp Protocol.
- ETS I EN 319 421 QTSP TSA policy.
- SQLCipher for on‑device encrypted DB.
- gRPC for low‑latency hash receipt.
Appendix A – Minimal Client ↔ Server handshake (pseudo‑sequence)
User taps Record
│
├─► RecordingService.startForeground()
│ ├─ CameraX open & startPreview
│ ├─ WebRtcClient.createOffer()
│ └─ Signal via WebSocket
│
Signalling Server ↔ offers/answers ICE
│
RTP/RTCP flow
│
Hasher splits GOP → SHA‑256 → TSA → OK
│
Server sends `hashReceipt` JSON over data‑channel ➜ App shows ✅
End of document