diff --git a/jicoco-mediajson/src/main/kotlin/org/jitsi/mediajson/MediaJson.kt b/jicoco-mediajson/src/main/kotlin/org/jitsi/mediajson/MediaJson.kt index 5bf37d4..e5b6ce6 100644 --- a/jicoco-mediajson/src/main/kotlin/org/jitsi/mediajson/MediaJson.kt +++ b/jicoco-mediajson/src/main/kotlin/org/jitsi/mediajson/MediaJson.kt @@ -154,7 +154,18 @@ data class Media( @JsonSerialize(using = Long2StringSerializer::class) @JsonDeserialize(using = String2LongDeserializer::class) val timestamp: Long, - val payload: String + val payload: String, + /** + * The RFC 6464 audio level of this frame (0-127, expressed as -dBov, i.e. 0 is loudest and 127 is silence), as + * reported by the sender in the ssrc-audio-level RTP header extension. Null when the sender did not include the + * extension. Encoded as a natural JSON number (not a string), unlike the VoxImplant-derived numeric fields above. + */ + val audioLevel: Int? = null, + /** + * The RFC 6464 Voice Activity Detection flag for this frame, as reported by the sender in the ssrc-audio-level RTP + * header extension. Null when the sender did not include the extension. + */ + val vad: Boolean? = null ) class Int2StringSerializer : JsonSerializer() { diff --git a/jicoco-mediajson/src/test/kotlin/org/jitsi/mediajson/MediaJsonTest.kt b/jicoco-mediajson/src/test/kotlin/org/jitsi/mediajson/MediaJsonTest.kt index 9d9b9f4..88ce2bc 100644 --- a/jicoco-mediajson/src/test/kotlin/org/jitsi/mediajson/MediaJsonTest.kt +++ b/jicoco-mediajson/src/test/kotlin/org/jitsi/mediajson/MediaJsonTest.kt @@ -97,6 +97,34 @@ class MediaJsonTest : ShouldSpec() { (parsed === event) shouldBe false } } + context("MediaEvent with audio level and VAD") { + val chunk = 213 + val timestamp = 0x1_0000_ffff + val payload = "p" + val event = MediaEvent(seq, Media(tag, chunk, timestamp, payload, audioLevel = 42, vad = true)) + + context("Serializing") { + val media = mapper.readTree(event.toJson()).get("media") + // Natural JSON number/boolean, not string-encoded like chunk/timestamp. + media.get("audioLevel").asInt() shouldBe 42 + media.get("vad").asBoolean() shouldBe true + } + context("Parsing") { + val parsed = Event.parse(event.toJson()) + (parsed == event) shouldBe true + parsed.shouldBeInstanceOf() + parsed.media.audioLevel shouldBe 42 + parsed.media.vad shouldBe true + } + context("Omitted when null") { + val bare = MediaEvent(seq, Media(tag, chunk, timestamp, payload)) + val media = mapper.readTree(bare.toJson()).get("media") + media.get("audioLevel") shouldBe null + media.get("vad") shouldBe null + bare.media.audioLevel shouldBe null + bare.media.vad shouldBe null + } + } context("PingEvent") { val id = 42 val event = PingEvent(id)