sbooth / SFBAudioEngine

A powerhouse of audio functionality for macOS and iOS
https://sbooth.github.io/SFBAudioEngine/
MIT License
552 stars 87 forks source link

Encoding .wav to .oggOpus #285

Closed said13 closed 8 months ago

said13 commented 8 months ago

Hi there. Thank you for your great library! I am having issues while converting .wav to .oggOpus. Conversion completes without errors, but the audio file has 0 duration. Here is my code:

                let outputSource = try OutputSource(for: outputURL)
                let encoder = try AudioEncoder(outputSource: outputSource, encoderName: .oggOpus)
                encoder.settings = [
                    .coreAudioFileTypeID: kAudioFileWAVEType,
                    .coreAudioFormatID: kAudioFormatLinearPCM,
                    .coreAudioAudioConverterPropertySettings: [kAudioConverterCodecQuality: kAudioConverterQuality_High],
                ]
                try AudioConverter.convert(inputURL, using: encoder)

Output url is an empty file with an extension "opus" which I create before converting.

UPD: Even without creating a file before the converting nothing changes.

sbooth commented 8 months ago

Does the app have write access to the file system? In my testing that is a common problem. Is there a line similar to fopen failed: Read-only file system (30) in the log?

Are there any other log messages displayed during encoding? What type of audio does inputURL contain?

The Ogg Opus encoder is a standalone encoder so the .coreAudio encoder settings don't apply. The Opus-specific settings are here.

said13 commented 8 months ago

Thank you for your fast answer! Writing is enabled, there is no problem with it.

This is the settings which I use for recording via AVAudiorecorder and it writes a file .wav

        let settings: [String: Any] = [
            AVFormatIDKey: Int(kAudioFormatLinearPCM),
            AVSampleRateKey: 44100,
            AVNumberOfChannelsKey: 1,
            AVLinearPCMBitDepthKey: 32,
            AVLinearPCMIsBigEndianKey: false,
            AVLinearPCMIsFloatKey: true
        ]

and this is the settings for converted(updated after your answer)

            encoder.settings = [
                AudioEncodingSettingsKey.opusPreserveSampleRate: 48000,
                AudioEncodingSettingsKey.opusComplexity: 10,
                AudioEncodingSettingsKey.opusSignalType: OpusSignalType.voice,
                AudioEncodingSettingsKey.opusFrameDuration: OpusFrameDuration.duration100ms,
                AudioEncodingSettingsKey.opusBitrate: 256,
                AudioEncodingSettingsKey.opusBitrateMode: OpusBitrateMode.VBR,
            ]

As a result I see a file with same content:

OggS���������ўђЈ����]OpusHead8Dђ�����OggS����������ўђЈ���@«ЦД€€юOpusTags���libopus 1.4, opus 1.4���5���ENCODER=SFBAudioEngine Ogg Opus Encoder (libopus 1.4)����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������OggS�8������ўђЈ`���Фmdрш€ю

I am doing something wrong or do I need any other code to convert it successfully?

sbooth commented 8 months ago

Could you share a short audio file that you're using for input? I created a mono 32-bit float WAV and was able to convert it with no issues.

What is the output of afinfo for the input file?

For me the following file

% afinfo ~/Desktop/untitled\ \#1.wav
File:           /Users/x/Desktop/untitled #1.wav
File type ID:   WAVE
Num Tracks:     1
----
Data format:     1 ch,  44100 Hz, Float32
                no channel layout.
estimated duration: 0.180000 sec
audio bytes: 31752
audio packets: 7938
bit rate: 1411200 bits per second
packet size upper bound: 4
maximum packet size: 4
audio data file offset: 44
optimized
source bit depth: F32
----

using the following settings

encoder.settings = [
    .opusPreserveSampleRate: true,
    .opusComplexity: 10,
    .opusSignalType: OpusSignalType.voice,
    .opusFrameDuration: OpusFrameDuration.duration100ms,
    .opusBitrate: 256,
    .opusBitrateMode: OpusBitrateMode.VBR,
]

converts correctly.

(Note that .opusPreserveSampleRate is meant to be a boolean value but any nonzero value is handled as true).

said13 commented 8 months ago

VoiceRecord_20240201210910_F3B08F9C-012B-4DC9-AE07-2300C1125BE1.wav.zip

Here is the file. Also the output of afinfo:

File type ID: WAVE Num Tracks: 1 Data format: 1 ch, 44100 Hz, Float32 no channel layout. estimated duration: 3.658322 sec audio bytes: 645328 audio packets: 161332 bit rate: 1411200 bits per second packet size upper bound: 4 maximum packet size: 4 audio data file offset: 4096 optimized source bit depth: F32

My code looks like this now:

            let outputSource = try OutputSource(for: outputURL)
            let encoder = try AudioEncoder(outputSource: outputSource, encoderName: .oggOpus)
            encoder.settings = [
                .opusPreserveSampleRate: true,
                .opusComplexity: 10,
                .opusSignalType: OpusSignalType.voice,
                .opusFrameDuration: OpusFrameDuration.duration100ms,
                .opusBitrate: 256,
                .opusBitrateMode: OpusBitrateMode.VBR,
            ]

            try AudioConverter.convert(inputURL, using: encoder)

Do I need to call any other functions to complete conversion?

sbooth commented 8 months ago

Does that produce a working opus file?

said13 commented 8 months ago

Unfortunately not.

sbooth commented 8 months ago

If you step through the conversion in the debugger where is it failing?

That file converts correctly using a slightly modified SimplePlayer-macOS:

--- a/SimplePlayer-macOS/AppDelegate.swift
+++ b/SimplePlayer-macOS/AppDelegate.swift
@@ -86,7 +86,7 @@ class AppDelegate: NSObject {
                openPanel.allowedFileTypes = PlayerWindowController.supportedPathExtensions

                if openPanel.runModal() == .OK, let url = openPanel.url {
-                       let destURL = url.deletingPathExtension().appendingPathExtension("wav")
+                       let destURL = url.deletingPathExtension().appendingPathExtension("opus")
                        if FileManager.default.fileExists(atPath: destURL.path) {
                                let alert = NSAlert()
                                alert.messageText = "Do you want to overwrite the existing file?"
@@ -100,7 +100,16 @@ class AppDelegate: NSObject {
                        }

                        do {
-                               try AudioConverter.convert(url, to: destURL)
+                               let encoder = try AudioEncoder(url: destURL, encoderName: .oggOpus)
+                               encoder.settings = [
+                                       .opusPreserveSampleRate: true,
+                                       .opusComplexity: 10,
+                                       .opusSignalType: OpusSignalType.voice,
+                                       .opusFrameDuration: OpusFrameDuration.duration100ms,
+                                       .opusBitrate: 256,
+                                       .opusBitrateMode: OpusBitrateMode.VBR,
+                               ]
+                               try AudioConverter.convert(url, using: encoder)
                                // Silently fail if metadata can't be copied
                                try? AudioFile.copyMetadata(from: url, to: destURL)
                        } catch let error {
said13 commented 8 months ago

Thank you @sbooth, it works as expected now!