-
Notifications
You must be signed in to change notification settings - Fork 45
Support wav audio
#244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support wav audio
#244
Conversation
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for WAV (lossless) audio recording format to the voice recorder application. Users can now select WAV as an output format in addition to the existing M4A, MP3, and OGG options.
Key changes:
- Implemented a new
WavRecorderclass that records audio in WAV format usingAudioRecordand writes PCM data with proper WAV headers - Added WAV as a new extension option with appropriate UI strings and format selection logic
- Hidden the bitrate setting when WAV format is selected, as it's a lossless format
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/main/res/values/donottranslate.xml | Added string resources for WAV format display |
| app/src/main/kotlin/org/fossify/voicerecorder/recorder/WavRecorder.kt | New recorder implementation for WAV format with PCM audio capture and header generation |
| app/src/main/kotlin/org/fossify/voicerecorder/services/RecorderService.kt | Updated recorder initialization logic to use WavRecorder when WAV format is selected |
| app/src/main/kotlin/org/fossify/voicerecorder/helpers/Constants.kt | Added WAV extension constant and sampling rate configuration |
| app/src/main/kotlin/org/fossify/voicerecorder/helpers/Config.kt | Added WAV format support in extension text getters and helper function |
| app/src/main/kotlin/org/fossify/voicerecorder/activities/SettingsActivity.kt | Updated settings UI to include WAV option and hide bitrate setting for lossless formats |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| if (fos != null) { | ||
| writeEmptyHeader(fos) | ||
| var chunksCount = 0 |
Copilot
AI
Nov 2, 2025
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable chunksCount is declared but never used. Consider removing it to improve code clarity.
| while (i < bufferSize) { | ||
| shortBuffer.put(data[i]) | ||
| shortBuffer.put(data[i + 1]) | ||
| sum += abs(shortBuffer.getShort(0).toInt()) | ||
| shortBuffer.clear() | ||
| i += 2 | ||
| } |
Copilot
AI
Nov 2, 2025
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential ArrayIndexOutOfBoundsException when bufferSize is odd. The loop accesses data[i + 1] which will fail if i equals bufferSize - 1. Add a check to ensure i + 1 < bufferSize or ensure bufferSize is always even.
| while (i < bufferSize) { | ||
| shortBuffer.put(data[i]) | ||
| shortBuffer.put(data[i + 1]) | ||
| sum += abs(shortBuffer.getShort(0).toInt()) | ||
| shortBuffer.clear() | ||
| i += 2 | ||
| } | ||
| amplitude.set((sum / (bufferSize / 16)).toInt()) |
Copilot
AI
Nov 2, 2025
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The amplitude calculation loop iterates over bufferSize bytes, but should iterate over read bytes instead. When audioRecord?.read() returns fewer bytes than bufferSize, this will process uninitialized or stale data from the data array. Change the condition to while (i < read).
| while (i < bufferSize) { | |
| shortBuffer.put(data[i]) | |
| shortBuffer.put(data[i + 1]) | |
| sum += abs(shortBuffer.getShort(0).toInt()) | |
| shortBuffer.clear() | |
| i += 2 | |
| } | |
| amplitude.set((sum / (bufferSize / 16)).toInt()) | |
| while (i < read) { | |
| shortBuffer.put(data[i]) | |
| shortBuffer.put(data[i + 1]) | |
| sum += abs(shortBuffer.getShort(0).toInt()) | |
| shortBuffer.clear() | |
| i += 2 | |
| } | |
| amplitude.set((sum / (read / 16)).toInt()) |
| private fun setWaveFileHeader(file: File, channels: Int) { | ||
| val fileSize = file.length() - 44 | ||
| val totalSize = fileSize + 36 | ||
| val sampleRate = context.config.samplingRate.toLong() | ||
| val byteRate = sampleRate * channels * (RECORDER_BPP / 8) | ||
|
|
||
| try { | ||
| val wavFile = RandomAccessFile(file, "rw") | ||
| wavFile.seek(0) | ||
| wavFile.write(generateHeader(fileSize, totalSize, sampleRate, channels, byteRate)) | ||
| wavFile.close() | ||
| } catch (e: FileNotFoundException) { | ||
| e.printStackTrace() | ||
| } catch (e: IOException) { | ||
| e.printStackTrace() | ||
| } | ||
| } |
Copilot
AI
Nov 2, 2025
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method setWaveFileHeader is never called anywhere in the codebase and duplicates logic already present in writeAudioDataToFile at lines 187-198. Consider removing this unused method.
| private fun setWaveFileHeader(file: File, channels: Int) { | |
| val fileSize = file.length() - 44 | |
| val totalSize = fileSize + 36 | |
| val sampleRate = context.config.samplingRate.toLong() | |
| val byteRate = sampleRate * channels * (RECORDER_BPP / 8) | |
| try { | |
| val wavFile = RandomAccessFile(file, "rw") | |
| wavFile.seek(0) | |
| wavFile.write(generateHeader(fileSize, totalSize, sampleRate, channels, byteRate)) | |
| wavFile.close() | |
| } catch (e: FileNotFoundException) { | |
| e.printStackTrace() | |
| } catch (e: IOException) { | |
| e.printStackTrace() | |
| } | |
| } |
| fun setOutputFilePath(path: String) { | ||
| recordFile = File(path) | ||
| } |
Copilot
AI
Nov 2, 2025
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The methods setOutputFile(path: String) and setOutputFilePath(path: String) have identical implementations. The redundant setOutputFilePath method should be removed, and callers should use the interface method setOutputFile(path: String) instead. Update RecorderService.kt lines 122 and 133 to call setOutputFile(recordingFile) directly.
| fun setOutputFilePath(path: String) { | |
| recordFile = File(path) | |
| } |
| .use { | ||
| recorder?.setOutputFile(it) | ||
| // For WavRecorder, also set the file path so it can update the header | ||
| if (recorder is WavRecorder) { | ||
| (recorder as WavRecorder).setOutputFilePath(recordingFile) | ||
| } | ||
| } |
Copilot
AI
Nov 2, 2025
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cast (recorder as WavRecorder) is redundant since the if check already confirms the type. Use a smart cast by storing the result: val wavRecorder = recorder as? WavRecorder and then call wavRecorder?.setOutputFilePath(recordingFile), or better yet, eliminate this special case by refactoring WavRecorder's API design.
|
|
||
| private fun getSamplingRatesArray(): ArrayList<Int> { | ||
| val baseRates = SAMPLING_RATES[config.extension]!! | ||
| // WAV doesn't have bitrate limits, return all available rates |
Copilot
AI
Nov 2, 2025
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The comment should clarify that WAV doesn't have bitrate-related sampling rate limits, not that it lacks bitrate limits entirely. Consider: 'WAV is lossless and doesn't have bitrate-related sampling rate limits'.
| // WAV doesn't have bitrate limits, return all available rates | |
| // WAV is lossless and doesn't have bitrate-related sampling rate limits, return all available rates |
|
Thanks, but AI-generated PRs aren't accepted as of now. |
|
@naveensingh Although AI was used in the development, everything was checked and tested by hand. This is good code implementing features me and other users have been looking for in this app. Could you clarify why this is being rejected without even being properly looked at? |
|
This is mentioned in the contribution guidelines: https://github.com/FossifyOrg/General-Discussion?tab=readme-ov-file#contributing-code There's no stance against AI here, but the current coding models aren't good enough for this project and lead to wasted review time. This may change in the future, but as of now, nearly all AI-generated PRs will be rejected. |
Type of change(s)
What changed and why
Tests performed
Before & after preview
Closes the following issue(s)
Checklist
CHANGELOG.md(if applicable).