Extracting audio from a video is one of the most common FFmpeg tasks — and one of the most misunderstood. Most tutorials show -acodec libmp3lame -b:a 192k which re-encodes the audio even when the original is already AAC or MP3. Re-encoding loses quality and wastes time. This guide covers the right approach: stream copy when possible, encode only when the target format requires it.

TL;DR

  • Keep original quality (AAC/M4A): ffmpeg -i input.mp4 -vn -acodec copy output.aac
  • Need MP3 specifically: ffmpeg -i input.mp4 -vn -acodec libmp3lame -q:a 2 output.mp3 (VBR ~190kbps, near-transparent)
  • No FFmpeg install: POST to the API with target=mp3

Why most audio extraction guides are wrong

MP4 files typically contain AAC audio. AAC is already a lossy compressed format — nearly everything encoded for streaming or smartphone recording is AAC. When you see a tutorial that does -acodec libmp3lame on an MP4, it's doing:

  1. Decompress AAC audio (lossy)
  2. Re-encode to MP3 (lossy again)

This is a generation loss — you're degrading audio that was already compressed. Unless you specifically need MP3 (e.g., a hardware device that doesn't support AAC), extract as AAC with stream copy. The file plays on every modern device and player.

Stream copy: lossless audio extraction

Stream copy (-acodec copy) re-muxes the audio bytes without touching them. Zero quality loss, nearly instant.

# MP4 → AAC (lossless stream copy)
ffmpeg -i input.mp4 -vn -acodec copy output.aac

# MP4 → M4A (same container, better compatibility with Apple)
ffmpeg -i input.mp4 -vn -acodec copy output.m4a

# MKV (often contains AAC or Opus) → extract audio stream
ffmpeg -i input.mkv -vn -acodec copy output.mka

# Inspect what audio codec is in your file first:
ffprobe -v quiet -print_format json -show_streams input.mp4 | grep codec_name

Stream copy fails if the audio codec in the source isn't compatible with the output container. For example, Opus audio can't be stream-copied into an .mp3 file. In that case, re-encode.

Encoding to MP3 (when you specifically need .mp3)

Some use cases require MP3: podcast platforms, car audio systems, older music players, certain WordPress audio players. Use VBR encoding with -q:a 2 for near-transparent quality (~190kbps average).

# Best quality MP3 (VBR ~190kbps, -q:a range: 0=best, 9=worst)
ffmpeg -i input.mp4 -vn -acodec libmp3lame -q:a 2 output.mp3

# Fixed 320kbps (for Spotify submission requirements)
ffmpeg -i input.mp4 -vn -acodec libmp3lame -b:a 320k output.mp3

# 192kbps — common streaming quality
ffmpeg -i input.mp4 -vn -acodec libmp3lame -b:a 192k output.mp3
import subprocess
from pathlib import Path

def extract_mp3(video_path: str, mp3_path: str, bitrate: str = None, vbr_quality: int = 2) -> None:
    """Extract MP3 from video. Use vbr_quality (0-9) or bitrate ('320k')."""
    cmd = ["ffmpeg", "-i", video_path, "-vn", "-acodec", "libmp3lame"]
    if bitrate:
        cmd += ["-b:a", bitrate]
    else:
        cmd += ["-q:a", str(vbr_quality)]
    cmd += ["-y", mp3_path]  # -y overwrites without asking
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        raise RuntimeError(f"FFmpeg error: {result.stderr[-500:]}")

extract_mp3("podcast_recording.mp4", "podcast.mp3", bitrate="192k")
extract_mp3("webinar.mp4", "webinar_audio.mp3")  # VBR quality 2

ChangeThisFile API

The API runs FFmpeg server-side. Handles MP4, MKV, WebM, MOV, AVI, FLV, and more. Source format is auto-detected from filename.

# MP4 to MP3
curl -X POST https://changethisfile.com/v1/convert \
  -H "Authorization: Bearer ctf_sk_your_key_here" \
  -F "file=@recording.mp4" \
  -F "target=mp3" \
  --output audio.mp3

# WebM to MP3
curl -X POST https://changethisfile.com/v1/convert \
  -H "Authorization: Bearer ctf_sk_your_key_here" \
  -F "file=@video.webm" \
  -F "target=mp3" \
  --output audio.mp3
import requests

API_KEY = "ctf_sk_your_key_here"

def extract_audio(video_path: str, out_path: str, target_format: str = "mp3") -> None:
    with open(video_path, "rb") as f:
        resp = requests.post(
            "https://changethisfile.com/v1/convert",
            headers={"Authorization": f"Bearer {API_KEY}"},
            files={"file": f},
            data={"target": target_format},
            timeout=180,
        )
    resp.raise_for_status()
    with open(out_path, "wb") as f:
        f.write(resp.content)

extract_audio("lecture.mp4", "lecture.mp3")
const fs = require('fs');
const FormData = require('form-data');
const fetch = require('node-fetch');

async function extractAudio(videoPath, outPath) {
  const form = new FormData();
  form.append('file', fs.createReadStream(videoPath));
  form.append('target', 'mp3');

  const res = await fetch('https://changethisfile.com/v1/convert', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer ctf_sk_your_key_here', ...form.getHeaders() },
    body: form,
  });
  if (!res.ok) throw new Error(await res.text());
  fs.writeFileSync(outPath, Buffer.from(await res.arrayBuffer()));
}

extractAudio('talk.mp4', 'talk.mp3');

Edge cases and gotchas

  • Video with no audio track. FFmpeg returns an error: "Output file does not contain any stream." Use ffprobe -show_streams to confirm audio exists before processing.
  • Multiple audio tracks. Some MKV files have multiple audio streams (e.g., English + Spanish). -acodec copy selects the first by default. Use -map 0:a:1 to select the second audio stream specifically.
  • Variable frame rate (VFR) video. Doesn't affect audio extraction — -vn drops video entirely so VFR is irrelevant.
  • DRM-protected video. Files from Netflix, iTunes purchases, etc. can't be processed by FFmpeg. The audio stream is encrypted. Only DRM-free files work.
  • Long videos time out on the API. The API timeout is 120 seconds. A 2-hour MP4 at high bitrate may exceed this. Split with ffmpeg -ss 0 -t 3600 -i input.mp4 -c copy part1.mp4 first.

Batch extraction script

#!/bin/bash
# Extract audio from all MP4 files in a folder
mkdir -p audio_output
for f in *.mp4; do
  base="${f%.mp4}"
  echo "Extracting: $f"
  ffmpeg -i "$f" -vn -acodec libmp3lame -q:a 2 -y "audio_output/${base}.mp3" 2>/dev/null
  echo "Done: audio_output/${base}.mp3"
done

For parallel batch processing (4 files at once):

ls *.mp4 | parallel -j4 'ffmpeg -i {} -vn -acodec libmp3lame -q:a 2 -y audio_output/{.}.mp3'

For quality-sensitive work, stream copy is always the right default. Only encode to MP3 when the downstream system requires it. Get a free API key for pipeline use without FFmpeg installs.