From 2454b04ef1939b34d99de81606e07c93beaa0d7f Mon Sep 17 00:00:00 2001 From: Jonathan Baudanza Date: Sun, 16 Jun 2024 07:05:12 -0400 Subject: [PATCH 1/2] Assign file extension based on audio format --- .../RNAudioRecorderPlayerModule.kt | 26 +++- index.ts | 3 + ios/RNAudioRecorderPlayer.swift | 138 +++++++++++++----- 3 files changed, 124 insertions(+), 43 deletions(-) diff --git a/android/src/main/java/com/dooboolab.audiorecorderplayer/RNAudioRecorderPlayerModule.kt b/android/src/main/java/com/dooboolab.audiorecorderplayer/RNAudioRecorderPlayerModule.kt index 994cd4cd..fb414209 100644 --- a/android/src/main/java/com/dooboolab.audiorecorderplayer/RNAudioRecorderPlayerModule.kt +++ b/android/src/main/java/com/dooboolab.audiorecorderplayer/RNAudioRecorderPlayerModule.kt @@ -60,7 +60,13 @@ class RNAudioRecorderPlayerModule(private val reactContext: ReactApplicationCont promise.reject("No permission granted.", "Try again after adding permission.") return } - audioFileURL = if (((path == "DEFAULT"))) "${reactContext.cacheDir}/$defaultFileName" else path + + var outputFormat = if (audioSet != null && audioSet.hasKey("OutputFormatAndroid")) + audioSet.getInt("OutputFormatAndroid") + else + MediaRecorder.OutputFormat.MPEG_4 + + audioFileURL = if (((path == "DEFAULT"))) "${reactContext.cacheDir}/sound.${defaultFileExtensions.get(outputFormat)}" else path _meteringEnabled = meteringEnabled if (mediaRecorder == null) { @@ -69,14 +75,14 @@ class RNAudioRecorderPlayerModule(private val reactContext: ReactApplicationCont if (audioSet != null) { mediaRecorder!!.setAudioSource(if (audioSet.hasKey("AudioSourceAndroid")) audioSet.getInt("AudioSourceAndroid") else MediaRecorder.AudioSource.MIC) - mediaRecorder!!.setOutputFormat(if (audioSet.hasKey("OutputFormatAndroid")) audioSet.getInt("OutputFormatAndroid") else MediaRecorder.OutputFormat.MPEG_4) + mediaRecorder!!.setOutputFormat(outputFormat) mediaRecorder!!.setAudioEncoder(if (audioSet.hasKey("AudioEncoderAndroid")) audioSet.getInt("AudioEncoderAndroid") else MediaRecorder.AudioEncoder.AAC) mediaRecorder!!.setAudioSamplingRate(if (audioSet.hasKey("AudioSamplingRateAndroid")) audioSet.getInt("AudioSamplingRateAndroid") else 48000) mediaRecorder!!.setAudioEncodingBitRate(if (audioSet.hasKey("AudioEncodingBitRateAndroid")) audioSet.getInt("AudioEncodingBitRateAndroid") else 128000) mediaRecorder!!.setAudioChannels(if (audioSet.hasKey("AudioChannelsAndroid")) audioSet.getInt("AudioChannelsAndroid") else 2) } else { mediaRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC) - mediaRecorder!!.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) + mediaRecorder!!.setOutputFormat(outputFormat) mediaRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AAC) mediaRecorder!!.setAudioEncodingBitRate(128000) mediaRecorder!!.setAudioSamplingRate(48000) @@ -381,5 +387,19 @@ class RNAudioRecorderPlayerModule(private val reactContext: ReactApplicationCont companion object { private var tag = "RNAudioRecorderPlayer" private var defaultFileName = "sound.mp4" + private var defaultFileExtensions = listOf( + "mp4", // DEFAULT = 0, + "3gp", // THREE_GPP, + "mp4", // MPEG_4, + "amr", // AMR_NB, + "amr", // AMR_WB, + "aac", // AAC_ADIF, + "aac", // AAC_ADTS, + "rtp", // OUTPUT_FORMAT_RTP_AVP, + "ts", // MPEG_2_TSMPEG_2_TS, + "webm",// WEBM + "xxx", // UNUSED + "ogg", // OGG + ) } } diff --git a/index.ts b/index.ts index 8eee36c4..0f80fbf0 100644 --- a/index.ts +++ b/index.ts @@ -34,6 +34,8 @@ export enum OutputFormatAndroidType { OUTPUT_FORMAT_RTP_AVP, MPEG_2_TS, WEBM, + UNUSED, + OGG, } export enum AudioEncoderAndroidType { @@ -44,6 +46,7 @@ export enum AudioEncoderAndroidType { HE_AAC, AAC_ELD, VORBIS, + OPUS, } export enum AVEncodingOption { diff --git a/ios/RNAudioRecorderPlayer.swift b/ios/RNAudioRecorderPlayer.swift index 0cce2a6a..83a988a0 100644 --- a/ios/RNAudioRecorderPlayer.swift +++ b/ios/RNAudioRecorderPlayer.swift @@ -184,55 +184,26 @@ class RNAudioRecorderPlayer: RCTEventEmitter, AVAudioRecorderDelegate { let avLPCMIsFloatKey = audioSets["AVLinearPCMIsFloatKeyIOS"] as? Bool let avLPCMIsNonInterleaved = audioSets["AVLinearPCMIsNonInterleavedIOS"] as? Bool - var avFormat: Int? = nil var avMode: AVAudioSession.Mode = AVAudioSession.Mode.default var sampleRate = audioSets["AVSampleRateKeyIOS"] as? Int var numberOfChannel = audioSets["AVNumberOfChannelsKeyIOS"] as? Int var audioQuality = audioSets["AVEncoderAudioQualityKeyIOS"] as? Int var bitRate = audioSets["AVEncoderBitRateKeyIOS"] as? Int - setAudioFileURL(path: path) - if (sampleRate == nil) { sampleRate = 44100; } - if (encoding == nil) { - avFormat = Int(kAudioFormatAppleLossless) + guard let avFormat: AudioFormatID = avFormat(fromString: encoding) else { + return reject("RNAudioPlayerRecorder", "Audio format not available", nil) + } + + if (path == "DEFAULT") { + let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first! + let fileExt = fileExtension(forAudioFormat: avFormat) + audioFileURL = cachesDirectory.appendingPathComponent("sound." + fileExt) } else { - if (encoding == "lpcm") { - avFormat = Int(kAudioFormatAppleIMA4) - } else if (encoding == "ima4") { - avFormat = Int(kAudioFormatAppleIMA4) - } else if (encoding == "aac") { - avFormat = Int(kAudioFormatMPEG4AAC) - } else if (encoding == "MAC3") { - avFormat = Int(kAudioFormatMACE3) - } else if (encoding == "MAC6") { - avFormat = Int(kAudioFormatMACE6) - } else if (encoding == "ulaw") { - avFormat = Int(kAudioFormatULaw) - } else if (encoding == "alaw") { - avFormat = Int(kAudioFormatALaw) - } else if (encoding == "mp1") { - avFormat = Int(kAudioFormatMPEGLayer1) - } else if (encoding == "mp2") { - avFormat = Int(kAudioFormatMPEGLayer2) - } else if (encoding == "mp4") { - avFormat = Int(kAudioFormatMPEG4AAC) - } else if (encoding == "alac") { - avFormat = Int(kAudioFormatAppleLossless) - } else if (encoding == "amr") { - avFormat = Int(kAudioFormatAMR) - } else if (encoding == "flac") { - if #available(iOS 11.0, *) { - avFormat = Int(kAudioFormatFLAC) - } - } else if (encoding == "opus") { - avFormat = Int(kAudioFormatOpus) - } else if (encoding == "wav") { - avFormat = Int(kAudioFormatLinearPCM) - } + setAudioFileURL(path: path) } if (mode == "measurement") { @@ -273,14 +244,14 @@ class RNAudioRecorderPlayer: RCTEventEmitter, AVAudioRecorderDelegate { func startRecording() { let settings = [ AVSampleRateKey: sampleRate!, - AVFormatIDKey: avFormat!, + AVFormatIDKey: avFormat, AVNumberOfChannelsKey: numberOfChannel!, AVEncoderAudioQualityKey: audioQuality!, AVLinearPCMBitDepthKey: avLPCMBitDepth ?? AVLinearPCMBitDepthKey.count, AVLinearPCMIsBigEndianKey: avLPCMIsBigEndian ?? true, AVLinearPCMIsFloatKey: avLPCMIsFloatKey ?? false, AVLinearPCMIsNonInterleaved: avLPCMIsNonInterleaved ?? false, - AVEncoderBitRateKey: bitRate! + AVEncoderBitRateKey: bitRate! ] as [String : Any] do { @@ -493,4 +464,91 @@ class RNAudioRecorderPlayer: RCTEventEmitter, AVAudioRecorderDelegate { audioPlayer.volume = volume resolve(volume) } + private func avFormat(fromString encoding: String?) -> AudioFormatID? { + if (encoding == nil) { + return kAudioFormatAppleLossless + } else { + if (encoding == "lpcm") { + return kAudioFormatAppleIMA4 + } else if (encoding == "ima4") { + return kAudioFormatAppleIMA4 + } else if (encoding == "aac") { + return kAudioFormatMPEG4AAC + } else if (encoding == "MAC3") { + return kAudioFormatMACE3 + } else if (encoding == "MAC6") { + return kAudioFormatMACE6 + } else if (encoding == "ulaw") { + return kAudioFormatULaw + } else if (encoding == "alaw") { + return kAudioFormatALaw + } else if (encoding == "mp1") { + return kAudioFormatMPEGLayer1 + } else if (encoding == "mp2") { + return kAudioFormatMPEGLayer2 + } else if (encoding == "mp4") { + return kAudioFormatMPEG4AAC + } else if (encoding == "alac") { + return kAudioFormatAppleLossless + } else if (encoding == "amr") { + return kAudioFormatAMR + } else if (encoding == "flac") { + if #available(iOS 11.0, *) { + return kAudioFormatFLAC + } + } else if (encoding == "opus") { + return kAudioFormatOpus + } else if (encoding == "wav") { + return kAudioFormatLinearPCM + } + } + return nil; + } + + private func fileExtension(forAudioFormat format: AudioFormatID) -> String { + switch format { + case kAudioFormatOpus: + return "ogg" + case kAudioFormatLinearPCM: + return "wav" + case kAudioFormatAC3, kAudioFormat60958AC3: + return "ac3" + case kAudioFormatAppleIMA4: + return "caf" + case kAudioFormatMPEG4AAC, kAudioFormatMPEG4CELP, kAudioFormatMPEG4HVXC, kAudioFormatMPEG4TwinVQ, kAudioFormatMPEG4AAC_HE, kAudioFormatMPEG4AAC_LD, kAudioFormatMPEG4AAC_ELD, kAudioFormatMPEG4AAC_ELD_SBR, kAudioFormatMPEG4AAC_ELD_V2, kAudioFormatMPEG4AAC_HE_V2, kAudioFormatMPEG4AAC_Spatial: + return "m4a" + case kAudioFormatMACE3, kAudioFormatMACE6: + return "caf" + case kAudioFormatULaw, kAudioFormatALaw: + return "wav" + case kAudioFormatQDesign, kAudioFormatQDesign2: + return "mov" + case kAudioFormatQUALCOMM: + return "qcp" + case kAudioFormatMPEGLayer1: + return "mp1" + case kAudioFormatMPEGLayer2: + return "mp2" + case kAudioFormatMPEGLayer3: + return "mp3" + case kAudioFormatMIDIStream: + return "mid" + case kAudioFormatAppleLossless: + return "m4a" + case kAudioFormatAMR: + return "amr" + case kAudioFormatAMR_WB: + return "awb" + case kAudioFormatAudible: + return "aa" + case kAudioFormatiLBC: + return "ilbc" + case kAudioFormatDVIIntelIMA, kAudioFormatMicrosoftGSM: + return "wav" + default: + // Generic file extension for types that don't have a natural + // file extension + return "audio" + } + } } From 5f41fe796e52742c48c1e79aeec425c0965d8676 Mon Sep 17 00:00:00 2001 From: Jonathan Baudanza Date: Mon, 17 Jun 2024 12:19:00 -0400 Subject: [PATCH 2/2] Remove commas for consistency --- .../RNAudioRecorderPlayerModule.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/dooboolab.audiorecorderplayer/RNAudioRecorderPlayerModule.kt b/android/src/main/java/com/dooboolab.audiorecorderplayer/RNAudioRecorderPlayerModule.kt index fb414209..3624b9fa 100644 --- a/android/src/main/java/com/dooboolab.audiorecorderplayer/RNAudioRecorderPlayerModule.kt +++ b/android/src/main/java/com/dooboolab.audiorecorderplayer/RNAudioRecorderPlayerModule.kt @@ -388,15 +388,15 @@ class RNAudioRecorderPlayerModule(private val reactContext: ReactApplicationCont private var tag = "RNAudioRecorderPlayer" private var defaultFileName = "sound.mp4" private var defaultFileExtensions = listOf( - "mp4", // DEFAULT = 0, - "3gp", // THREE_GPP, - "mp4", // MPEG_4, - "amr", // AMR_NB, - "amr", // AMR_WB, - "aac", // AAC_ADIF, - "aac", // AAC_ADTS, - "rtp", // OUTPUT_FORMAT_RTP_AVP, - "ts", // MPEG_2_TSMPEG_2_TS, + "mp4", // DEFAULT = 0 + "3gp", // THREE_GPP + "mp4", // MPEG_4 + "amr", // AMR_NB + "amr", // AMR_WB + "aac", // AAC_ADIF + "aac", // AAC_ADTS + "rtp", // OUTPUT_FORMAT_RTP_AVP + "ts", // MPEG_2_TSMPEG_2_TS "webm",// WEBM "xxx", // UNUSED "ogg", // OGG