Skip to content

Commit f039b9e

Browse files
committed
audio works with no autosampling
1 parent 72cda1b commit f039b9e

File tree

14 files changed

+605
-112
lines changed

14 files changed

+605
-112
lines changed

app/mobile/Cargo.lock

Lines changed: 287 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/mobile/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ tauri-plugin-deep-link = "2"
3232
tauri-plugin-web-auth = "1"
3333
tauri-plugin-iap = "0.7"
3434
tauri-plugin-opener = "2"
35+
tauri-plugin-log = "2"
3536

3637
# HTTP server for broadcast extension and proxy
3738
tokio = { version = "1", features = ["full"] }

app/mobile/gen/apple/broadcast/SampleHandler.swift

Lines changed: 107 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ class SampleHandler: RPBroadcastSampleHandler {
162162
AudioRingBuffer.shared.write(samples: samples, sampleRate: sampleRate, timestamp: timestamp)
163163
}
164164

165+
private var audioFormatLogged = false
166+
165167
private func audioBufferToFloats(_ sampleBuffer: CMSampleBuffer) -> [Float]? {
166168
var audioBufferList = AudioBufferList()
167169
var blockBuffer: CMBlockBuffer?
@@ -190,33 +192,121 @@ class SampleHandler: RPBroadcastSampleHandler {
190192

191193
let sourceFormat = streamDescription.pointee.mFormatFlags
192194
let bytesPerSample = Int(streamDescription.pointee.mBitsPerChannel / 8)
193-
let numSamples = dataSize / bytesPerSample
195+
let channelCount = Int(streamDescription.pointee.mChannelsPerFrame)
196+
let sampleRate = streamDescription.pointee.mSampleRate
197+
let bytesPerFrame = Int(streamDescription.pointee.mBytesPerFrame)
198+
let framesPerPacket = streamDescription.pointee.mFramesPerPacket
199+
let isFloat = (sourceFormat & kAudioFormatFlagIsFloat) != 0
200+
let isInterleaved = (sourceFormat & kAudioFormatFlagIsNonInterleaved) == 0
201+
202+
// Log format once for debugging
203+
if !audioFormatLogged {
204+
audioFormatLogged = true
205+
DebugLog.shared.log("🎵 Audio Format Debug:")
206+
DebugLog.shared.log(" - Sample Rate: \(sampleRate) Hz")
207+
DebugLog.shared.log(" - Channels: \(channelCount)")
208+
DebugLog.shared.log(" - Bits/Channel: \(streamDescription.pointee.mBitsPerChannel)")
209+
DebugLog.shared.log(" - Bytes/Sample: \(bytesPerSample)")
210+
DebugLog.shared.log(" - Bytes/Frame: \(bytesPerFrame)")
211+
DebugLog.shared.log(" - Frames/Packet: \(framesPerPacket)")
212+
DebugLog.shared.log(" - Is Float: \(isFloat)")
213+
DebugLog.shared.log(" - Is Interleaved: \(isInterleaved)")
214+
DebugLog.shared.log(" - Data size: \(dataSize) bytes")
215+
DebugLog.shared.log(" - Format flags: 0x\(String(sourceFormat, radix: 16))")
216+
217+
// Calculate frames for this first chunk
218+
let firstNumFrames = dataSize / bytesPerFrame
219+
DebugLog.shared.log(" - Frames per chunk: \(firstNumFrames)")
220+
DebugLog.shared.log(" - Output: \(firstNumFrames) mono f32 samples (\(firstNumFrames * 4) bytes)")
221+
if channelCount == 2 {
222+
DebugLog.shared.log(" ✅ Stereo-to-mono downmix enabled")
223+
}
224+
}
194225

195-
// Convert to f32 PCM
196-
var floatData = [Float](repeating: 0, count: numSamples)
226+
// Calculate number of FRAMES (a frame contains samples for all channels)
227+
let numFrames = dataSize / bytesPerFrame
228+
229+
// Output is MONO - one float per frame (downmix stereo if needed)
230+
var floatData = [Float](repeating: 0, count: numFrames)
197231

198232
// Check if source is already float
199-
if (sourceFormat & kAudioFormatFlagIsFloat) != 0 {
200-
// Already float, just copy
201-
if bytesPerSample == 4 {
202-
mData.withMemoryRebound(to: Float.self, capacity: numSamples) { ptr in
203-
floatData = Array(UnsafeBufferPointer(start: ptr, count: numSamples))
233+
if isFloat {
234+
if channelCount == 1 {
235+
// Mono float - direct copy
236+
mData.withMemoryRebound(to: Float.self, capacity: numFrames) { ptr in
237+
floatData = Array(UnsafeBufferPointer(start: ptr, count: numFrames))
238+
}
239+
} else if channelCount == 2 && isInterleaved {
240+
// Stereo float interleaved - downmix to mono
241+
mData.withMemoryRebound(to: Float.self, capacity: numFrames * 2) { ptr in
242+
for i in 0..<numFrames {
243+
let left = ptr[i * 2]
244+
let right = ptr[i * 2 + 1]
245+
floatData[i] = (left + right) * 0.5
246+
}
204247
}
205248
}
206249
} else {
207-
// Convert from integer to float
250+
// Integer format - convert to float
251+
// Check endianness from format flags
252+
let isBigEndian = (sourceFormat & kAudioFormatFlagIsBigEndian) != 0
253+
208254
if bytesPerSample == 2 {
209-
// Int16 to Float
210-
mData.withMemoryRebound(to: Int16.self, capacity: numSamples) { ptr in
211-
for i in 0..<numSamples {
212-
floatData[i] = Float(ptr[i]) / Float(Int16.max)
255+
// Int16 format
256+
if channelCount == 1 {
257+
// Mono Int16
258+
mData.withMemoryRebound(to: Int16.self, capacity: numFrames) { ptr in
259+
for i in 0..<numFrames {
260+
var sample = ptr[i]
261+
if isBigEndian {
262+
sample = Int16(bigEndian: sample)
263+
}
264+
floatData[i] = Float(sample) / Float(Int16.max)
265+
}
266+
}
267+
} else if channelCount == 2 && isInterleaved {
268+
// Stereo Int16 interleaved - downmix to mono
269+
mData.withMemoryRebound(to: Int16.self, capacity: numFrames * 2) { ptr in
270+
for i in 0..<numFrames {
271+
var leftRaw = ptr[i * 2]
272+
var rightRaw = ptr[i * 2 + 1]
273+
if isBigEndian {
274+
leftRaw = Int16(bigEndian: leftRaw)
275+
rightRaw = Int16(bigEndian: rightRaw)
276+
}
277+
let left = Float(leftRaw) / Float(Int16.max)
278+
let right = Float(rightRaw) / Float(Int16.max)
279+
floatData[i] = (left + right) * 0.5
280+
}
213281
}
214282
}
215283
} else if bytesPerSample == 4 {
216-
// Int32 to Float
217-
mData.withMemoryRebound(to: Int32.self, capacity: numSamples) { ptr in
218-
for i in 0..<numSamples {
219-
floatData[i] = Float(ptr[i]) / Float(Int32.max)
284+
// Int32 format
285+
if channelCount == 1 {
286+
// Mono Int32
287+
mData.withMemoryRebound(to: Int32.self, capacity: numFrames) { ptr in
288+
for i in 0..<numFrames {
289+
var sample = ptr[i]
290+
if isBigEndian {
291+
sample = Int32(bigEndian: sample)
292+
}
293+
floatData[i] = Float(sample) / Float(Int32.max)
294+
}
295+
}
296+
} else if channelCount == 2 && isInterleaved {
297+
// Stereo Int32 interleaved - downmix to mono
298+
mData.withMemoryRebound(to: Int32.self, capacity: numFrames * 2) { ptr in
299+
for i in 0..<numFrames {
300+
var leftRaw = ptr[i * 2]
301+
var rightRaw = ptr[i * 2 + 1]
302+
if isBigEndian {
303+
leftRaw = Int32(bigEndian: leftRaw)
304+
rightRaw = Int32(bigEndian: rightRaw)
305+
}
306+
let left = Float(leftRaw) / Float(Int32.max)
307+
let right = Float(rightRaw) / Float(Int32.max)
308+
floatData[i] = (left + right) * 0.5
309+
}
220310
}
221311
}
222312
}

app/mobile/gen/apple/broadcast/VideoFrameBuffer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class VideoFrameBuffer {
129129

130130
// Write JPEG data to buffer section
131131
let dataStart = data.advanced(by: headerSize)
132-
jpegData.withUnsafeBytes { srcBuffer in
132+
_ = jpegData.withUnsafeBytes { srcBuffer in
133133
memcpy(dataStart, srcBuffer.baseAddress!, Int(frameSize))
134134
}
135135

app/mobile/gen/schemas/acl-manifests.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

app/mobile/gen/schemas/iOS-schema.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2852,6 +2852,24 @@
28522852
"const": "ios-keyboard:deny-ping",
28532853
"markdownDescription": "Denies the ping command without any pre-configured scope."
28542854
},
2855+
{
2856+
"description": "Allows the log command\n#### This default permission set includes:\n\n- `allow-log`",
2857+
"type": "string",
2858+
"const": "log:default",
2859+
"markdownDescription": "Allows the log command\n#### This default permission set includes:\n\n- `allow-log`"
2860+
},
2861+
{
2862+
"description": "Enables the log command without any pre-configured scope.",
2863+
"type": "string",
2864+
"const": "log:allow-log",
2865+
"markdownDescription": "Enables the log command without any pre-configured scope."
2866+
},
2867+
{
2868+
"description": "Denies the log command without any pre-configured scope.",
2869+
"type": "string",
2870+
"const": "log:deny-log",
2871+
"markdownDescription": "Denies the log command without any pre-configured scope."
2872+
},
28552873
{
28562874
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
28572875
"type": "string",

app/mobile/gen/schemas/mobile-schema.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2852,6 +2852,24 @@
28522852
"const": "ios-keyboard:deny-ping",
28532853
"markdownDescription": "Denies the ping command without any pre-configured scope."
28542854
},
2855+
{
2856+
"description": "Allows the log command\n#### This default permission set includes:\n\n- `allow-log`",
2857+
"type": "string",
2858+
"const": "log:default",
2859+
"markdownDescription": "Allows the log command\n#### This default permission set includes:\n\n- `allow-log`"
2860+
},
2861+
{
2862+
"description": "Enables the log command without any pre-configured scope.",
2863+
"type": "string",
2864+
"const": "log:allow-log",
2865+
"markdownDescription": "Enables the log command without any pre-configured scope."
2866+
},
2867+
{
2868+
"description": "Denies the log command without any pre-configured scope.",
2869+
"type": "string",
2870+
"const": "log:deny-log",
2871+
"markdownDescription": "Denies the log command without any pre-configured scope."
2872+
},
28552873
{
28562874
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
28572875
"type": "string",

0 commit comments

Comments
 (0)