AVAudioRecorder on Android diverges from AVFoundation (currentTime sign, metering, record/resume)
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 14
- Forks
- 12
- PR merge metrics
- No merged PRs in 30d
Description
While evaluating Skip for an app that records audio, I went through Sources/SkipAV/AVAudioRecorder.swift against the AVFoundation headers and found six places where the Android implementation does not behave the way the iOS API it stands in for does. They are all in the #elseif SKIP branch, so iOS is unaffected.
Quoting AVFAudio.framework/Headers/AVAudioRecorder.h for reference:
- (BOOL)record; // Start or resume recording to file.
@property(readonly) NSTimeInterval currentTime;
@property(getter=isMeteringEnabled) BOOL meteringEnabled;
- (void)updateMeters;
- (float)peakPowerForChannel:(NSUInteger)channelNumber;
- (float)averagePowerForChannel:(NSUInteger)channelNumber;
1. currentTime returns a negative value
public var currentTime: TimeInterval {
if let startTime = recordingStartTime {
return startTime.timeIntervalSinceNow // negative once recording has started
}
timeIntervalSinceNow measures from the start date to now, so it is negative for a date in the past. AVFoundation reports a positive elapsed duration. Any elapsed-time label bound to this shows a countdown into negative numbers on Android.
Related: the elapsed time also restarts from zero after pause(), since nothing accumulates the earlier segments.
2. averagePower(forChannel:) returns Double, not Float
The header declares float for both power accessors. Cross-platform code written against the iOS signature (let level: Float = recorder.averagePower(forChannel: 0)) compiles on iOS and then fails to compile once transpiled to Kotlin.
3. Power is reported as a 0...1 linear ratio instead of decibels
return Float(recorder?.maxAmplitude ?? 0) / Float(32767.0)
AVFoundation returns decibels relative to full scale, -160.0 for silence through 0.0 at full scale. A level meter calibrated for the iOS range renders incorrectly on Android — the values are always positive and compress the quiet end of the range.
4. isMeteringEnabled and updateMeters() are missing, so metering cannot be used at all
@available(*, unavailable)
public var meteringEnabled = false
Two problems: the property uses the Objective-C name rather than the name Swift imports (isMeteringEnabled), and it is marked unavailable. There is no updateMeters(). The standard iOS sequence therefore does not compile on Android:
recorder.isMeteringEnabled = true
recorder.updateMeters()
let level = recorder.averagePower(forChannel: 0)
There is a second reason updateMeters() matters here: MediaRecorder.getMaxAmplitude() returns the maximum amplitude sampled since it was last called and resets on every read. Since both accessors read it directly today, calling peak and average in the same frame makes whichever runs second observe silence.
5. record() restarts the recording instead of resuming, and returns Void
public func record() {
do {
prepareToRecord() // builds a new MediaRecorder over the same output file
recorder?.start()
prepareToRecord() runs on every call, which constructs a fresh MediaRecorder and re-opens the output file. pause() followed by record() therefore discards everything captured before the pause, where the header says record should "start or resume". MediaRecorder.resume() is the counterpart to the pause() already in use.
record() also returns Void rather than BOOL, so there is no way to detect a failed start.
6. AVNumberOfChannelsKey is ignored, and numeric settings only match Int
setAudioChannels(2) // hardcoded
setAudioSamplingRate(settings["AVSampleRateKey"] as? Int ?? 44100)
Mono cannot be requested. The Showcase app's AudioPlayground already passes AVNumberOfChannelsKey: 1 and records in stereo on Android.
Separately, AVFoundation documents these settings as NSNumber values and iOS code commonly writes AVSampleRateKey: 44100.0. Matching only against Int makes a Double fall through to the default silently.
Related observations, not part of this report
MediaRecorder(Context)requires API 31, but the CI workflow runs the emulator at API 28.AVAudioRecorderDelegate's methods are required here; on iOS they are@optional.AVFormatIDKeyis accepted but ignored.MediaRecorderhas no linear PCM output, so only AAC/MPEG-4 is reachable — worth documenting rather than implementing.record(atTime:)andrecord(forDuration:)are not implemented.AudioFormat,MediaPlayer, andFileOutputStreamare imported but unused.
I have a branch that addresses items 1–6 with a test for the metering conversion and a README update, and will open it as a draft PR shortly. Happy to split it up or drop pieces if you'd prefer a smaller change — items 3 and 5 do change behavior for existing Android users, so I'd understand wanting those handled separately.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read Sources/SkipAV/AVAudioRecorder.swift, focusing on the #elseif SKIP branch, then inspect the metering-conversion test and README update on the reported branch. Done means Android behavior matches the cited AVFoundation signatures and semantics for items 1–6, with the metering test and documentation update completed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- audio-video-rtc, mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100