Skip to content

Instantly share code, notes, and snippets.

@IsaMorphic
Created December 30, 2022 16:26
Show Gist options
  • Save IsaMorphic/0bbc913905afc553e3a21964cd5bb49a to your computer and use it in GitHub Desktop.
Save IsaMorphic/0bbc913905afc553e3a21964cd5bb49a to your computer and use it in GitHub Desktop.
FFMediaToolkit Example - Remux M4V+MP3 to MP4
using FFMediaToolkit.Audio;
using FFMediaToolkit.Decoding;
using FFMediaToolkit.Encoding;
using FFMediaToolkit.Graphics;
// File paths, grab from anywhere like command-line args, config file, etc.
string inputVideoFilePath = "video-mute.m4v";
string inputAudioFilePath = "audio.mp3";
string outputMediaFilePath = "video-dubbed.mp4";
// Open input media containers.
// Expects the following inputs:
// - M4V video container
// - MP3 audio container
using var inputVideoFile = MediaFile.Open(
Path.GetFullPath(inputVideoFilePath),
new MediaOptions { StreamsToLoad = MediaMode.Video } // Tell FFMediaToolkit we are only interested in video data from this container.
);
using var inputAudioFile = MediaFile.Open(
Path.GetFullPath(inputAudioFilePath),
new MediaOptions { StreamsToLoad = MediaMode.Audio } // Tell FFMediaToolkit we are only interested in audio data from this container.
);
var videoInfo = inputVideoFile.Video.Info; // Grab codec information for input video stream.
var audioInfo = inputAudioFile.Audio.Info; // IMPORTANT: Grab codec information for input audio stream; contains crucial parameter values.
// Now we initialize the output container
using var outputMediaFile = MediaBuilder.CreateContainer(Path.GetFullPath(outputMediaFilePath))
.WithVideo( // single video stream with same params as input video, same codec
new VideoEncoderSettings(videoInfo.FrameSize.Width, videoInfo.FrameSize.Height)
{ FramerateRational = videoInfo.RealFrameRate } // Just in case contents are variable or non-integer framerate
)
// THIS IS THE CRITICAL SECTION OF CONFIGURATION!!
// All audio codecs in FFmpeg have specific restrictions on which sample rates, formats, and number of channels they can use.
// Additionally, like video streams, FFmpeg splits audio streams into "frames".
// These audio frames each contain the same number of samples, as specified by SamplesPerFrame in FFMediaToolkit.
// SampleFormat and SamplesPerFrame must be configured manually at the moment when using FFMediaToolkit for the
// library to properly encode audio.
// IMPORTANT NOTE:
// FFMediaTookit's API exposes an AudioData structure, much like ImageData. It contains members to extract and update audio frame data
// as float[][], where the first index is for which audio channel you want to access, and the second is for which sample in that channel.
// To read audio frame data, use the methods AudioData.GetChannelData() & AudioData.GetSampleData().
// To modify audio frame data, use the methods AudioData.UpdateChannelData() & AudioData.UpdateSampleData().
.WithAudio(
new AudioEncoderSettings(audioInfo.SampleRate, audioInfo.NumChannels, AudioCodec.MP3)
{
SampleFormat = audioInfo.SampleFormat,
SamplesPerFrame = audioInfo.SamplesPerFrame,
}
)
.Create(); // Whew! That was a lot of explanation! Let's finish this!
// Store references to input & output streams in variables with shorthand names.
var videoIn = inputVideoFile.Video;
var audioIn = inputAudioFile.Audio;
var videoOut = outputMediaFile.Video;
var audioOut = outputMediaFile.Audio;
// Next, to properly mux the file with audio and video in sync, we need to use one stream as a "pacer" and the other as a "follower".
// Let's use the video as the "pacer".
bool stillMoreAudio = true;
while (videoIn.TryGetNextFrame(out ImageData imageData)) // Get next video frame as long as its available.
// This sets the video stream ahead of the audio stream.
{
videoOut.AddFrame(imageData, videoIn.Position); // Add it to the output container.
// Now, because audio frames are shorter than video frames, we remux as many as we can until we catch up to the video stream again.
while(audioIn.Position < videoIn.Position && (stillMoreAudio = audioIn.TryGetNextFrame(out AudioData audioData)))
{
// If the input SamplesPerFrame were different than the output SamplesPerFrame,
// you'd need to implement some extra buffering logic here to redistribute the data.
outputMediaFile.Audio.AddFrame(audioData, audioIn.Position);
}
if (!stillMoreAudio) break; // in case there's no more audio, we bail out!
}
// Stop at end of shortest input file.
return; // Now we're done! Because we prefixed our IDisposable declarations with "using", application will clean up for us.
// Don't forget to do this manually if necessary!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment