How to Merge Audio Files in .NET Core with FFmpeg by Pratham Ghosalkar

🎵 Why I Built an Audio Mixer

I’m a .NET developer, and my boss gave me a cool task: let users pick audio files (like call recordings), merge them into one MP3, and download it. Easy, right? Not quite! 😅 I needed to:

  • Let users select audio files with checkboxes.

  • Send the files to the server.

  • Use FFmpeg to combine them.

  • Provide a downloadable MP3.

It was a fun challenge with some sneaky problems. Here’s my simple guide to make it work, including a big issue I fixed with mixed audio formats.


🛠️ How I Did It

🎯 Step 1: Get FFmpeg

FFmpeg is the tool that merges audio. I downloaded and set it up (check my if you need help)

🔗 How to Download FFMPEG

To make sure FFmpeg is included when building or publishing, add this to your .csproj file:

<ItemGroup>
    <None Include="FFMPEG\**\*">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </None>
</ItemGroup>

📁 Step 2: Where to Put FFmpeg

I first put FFmpeg in the bin folder . Bad move. When I published the app, bin got wiped, and FFmpeg disappeared. 😞

Fix: I moved FFmpeg to the project root:

MyApp
│
├── Controllers
├── Views
├── wwwroot
├── FFMPEG
│   └── bin 
          └── ffmpeg.exe

Then I coded it to find FFmpeg:

string ffmpegPath = Path.Combine(AppContext.BaseDirectory, "FFMPEG", "bin", "ffmpeg.exe");

Now FFmpeg sticks around after publishing. Awesome! 🎉


💻 Backend: Merging the Audio

Here’s the updated code to merge audio files, including a fix for a tricky issue with mixed formats:

public static async Task<byte[]> MergeAudioFilesAsync(string[] inputPaths)
        {
            string tempDir = null;
            try
            {
                var existingFiles = inputPaths.Where(System.IO.File.Exists).ToList();

                if (existingFiles.Count < 2)
                    throw new ArgumentException("At least two valid files are required to merge.");

                string projectRoot = AppDomain.CurrentDomain.BaseDirectory;
                tempDir = Path.Combine(projectRoot, "MergedAudios", Guid.NewGuid().ToString());
                Directory.CreateDirectory(tempDir);

                string ffmpegPath = Path.Combine(AppContext.BaseDirectory, "FFMPEG", "bin", "ffmpeg.exe");

                // Step 1: Transcode all to WAV (lossless, accurate)
                List<string> wavPaths = new List<string>();
                for (int i = 0; i < existingFiles.Count; i++)
                {
                    string inputFile = existingFiles[i];
                    string outputWav = Path.Combine(tempDir, $"audio_{i}.wav");

                    var psi = new ProcessStartInfo
                    {
                        FileName = ffmpegPath,
                        Arguments = $"-y -i \"{inputFile}\" -ac 2 -ar 44100 \"{outputWav}\"",
                        RedirectStandardError = true,
                        RedirectStandardOutput = true,
                        UseShellExecute = false,
                        CreateNoWindow = true
                    };

                    using (var process = new Process { StartInfo = psi })
                    {
                        process.Start();
                        string errorOutput = await process.StandardError.ReadToEndAsync();
                        await process.WaitForExitAsync();

                        if (process.ExitCode != 0)
                            throw new Exception($"FFmpeg transcoding failed for {inputFile}: {errorOutput}");
                    }

                    wavPaths.Add(outputWav);
                }

                // Step 2: Create concat list for WAV files
                string listPath = Path.Combine(tempDir, "list.txt");
                await File.WriteAllLinesAsync(listPath, wavPaths.Select(p => $"file '{p.Replace("\\", "/")}'"));

                // Step 3: Merge WAVs into one big WAV
                string combinedWav = Path.Combine(tempDir, "combined.wav");
                var mergeWavPsi = new ProcessStartInfo
                {
                    FileName = ffmpegPath,
                    Arguments = $"-f concat -safe 0 -i \"{listPath}\" -c copy \"{combinedWav}\"",
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                };

                using (var process = new Process { StartInfo = mergeWavPsi })
                {
                    process.Start();
                    string errorOutput = await process.StandardError.ReadToEndAsync();
                    await process.WaitForExitAsync();

                    if (process.ExitCode != 0)
                        throw new Exception($"FFmpeg WAV merge failed: {errorOutput}");
                }

                // Step 4: Convert merged WAV to final MP3
                string finalMp3 = Path.Combine(tempDir, "merged.mp3");
                var mp3ConvertPsi = new ProcessStartInfo
                {
                    FileName = ffmpegPath,
                    Arguments = $"-y -i \"{combinedWav}\" -acodec libmp3lame -b:a 192k \"{finalMp3}\"",
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                };

                using (var process = new Process { StartInfo = mp3ConvertPsi })
                {
                    process.Start();
                    string errorOutput = await process.StandardError.ReadToEndAsync();
                    await process.WaitForExitAsync();

                    if (process.ExitCode != 0)
                        throw new Exception($"FFmpeg MP3 export failed: {errorOutput}");
                }
                // Step 5: Return final merged audio bytes
                return await File.ReadAllBytesAsync(finalMp3);
            }
            catch (Exception ex)
            {
                throw new Exception($"Merging audio files failed: {ex.Message}", ex);
            }
            finally
            {
              //clean the directory
            }
        }

🎨 Frontend: Fixing Problems

😩 Blank Page Issue

My JavaScript had this:

target: 'hiddenDownloader'

It opened a blank page after download. Not cool. Fix: Removed target:

var $form = $('<form>', { method: 'POST', action: appurl + '/DataView/AudioMerging' });

Now downloads work on the same page. Sweet! 😎

😄 Making It User-Friendly

  • Added a loading spinner to show progress.

  • Show an error if users pick too few files.

  • Named the MP3 with the date, like MergedAudio_2025-04-20_123456.mp3.


🎧 Fixing Mixed Audio Types

😣 Problem: Duration Loss with Mixed Formats

Users uploaded MP3, WAV, and G.729 (a VoIP format) files. When I merged them, some audio got cut off, especially with MP3s. The result was incomplete or glitchy recordings. Not good! 😬

Why? MP3s use lossy compression, which messes with timing when merged with other formats like WAV or G.729. FFmpeg struggled to sync them properly.

😊 Fix: Convert to WAV First

To fix this, I changed the process:

  1. Convert all files to WAV (a lossless format) first.

  2. Merge the WAV files.

  3. Convert the final result to MP3.

This solved the timing issues and kept all audio intact. No more missing chunks! 🎵 The updated code above shows how I did it.


🏆 What I Learned

  • bin folder wipes files when publishing. Don’t use it!

  • JavaScript forms can be tricky but are fixable.

  • FFmpeg commands are like magic—learn them well.

  • Converting to WAV before merging saves the day for mixed formats.

  • Logging helps track what’s happening.


Why It’s Awesome

This feature is live, and my team loves it. They merge audio files easily, even with weird formats like G.729. I also got better at:

  • Audio processing

  • Mixing .NET and JavaScript

  • Handling files in production


🙌 Need Help?

Stuck? Drop a comment, and let’s sort it out over virtual snacks. 🍪💻 Hope this guide helps you merge audio like a pro!

What’s your toughest coding challenge? Share it!

10
Subscribe to my newsletter

Read articles from Pratham Ghosalkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pratham Ghosalkar
Pratham Ghosalkar