Demux Audio and Video From MP4

This topic describes how to use the Transcoder::run method to extract the first audio and video streams from an MP4 container and save each stream into a separate MP4 file.

The code snippets in this article are from the demux_mp4_file macOS sample.

Linux and Windows samples are also available:

Source File

For source we use the big_buck_bunny_trailer.mp4 file from the AVBlocks Assets repository. After downloading and unzipping you will find it in the mov subdirectory.

Code

This code extracts the first audio and video elementary streams from an MP4 container and writes them to separate MP4 files.

Initialize AVBlocks

Initialize the AVBlocks library before creating the transcoder, and shut it down after the demuxing operation is complete.

int main(int argc, char* argv[])
{
	Options opt;

	switch(prepareOptions(opt, argc, argv))
	{
		case Command: return 0;
		case Error:	return 1;
        case Parsed: break;
	}

    Library::initialize();

	bool demuxResult = demuxMP4(opt);

    Library::shutdown();

	return demuxResult ? 0 : 1;
}

Read the Input Container

The sample uses MediaInfo to inspect the MP4 input file and create the input socket from the discovered stream information.

primo::ref<Transcoder> configureTranscoder(Options& opt)
{
    primo::ref<MediaInfo> info(Library::createMediaInfo());
    info->inputs()->at(0)->setFile(primo::ustring(opt.inputFile));

    if (!info->open())
    {
        printError("MediaInfo open", info->error());
        return primo::ref<Transcoder>();
    }

    primo::ref<MediaSocket> inSocket(Library::createMediaSocket(info.get()));

    info->close();

    // ...existing code...
}

Select Audio and Video Streams

The sample keeps the first audio stream and the first video stream. Any additional pins are disabled.

    bool audio = false;
    bool video = false;

    for (int i = 0; i < inSocket->pins()->count(); ++i)
    {
        string fileName;
        if (inSocket->pins()->at(i)->streamInfo()->mediaType() == MediaType::Audio && !audio)
        {
            audio = true;
            fileName = opt.outputFile + ".aud.mp4";
        }
        else if (inSocket->pins()->at(i)->streamInfo()->mediaType() == MediaType::Video && !video)
        {
            video = true;
            fileName = opt.outputFile + ".vid.mp4";
        }
        else
        {
            inSocket->pins()->at(i)->setConnection(PinConnection::Disabled);
            continue;
        }

        // ...existing code...
    }

Create Output Sockets

For each selected stream, the sample creates an output socket, attaches the corresponding input pin, sets the output file, and adds the socket to the transcoder outputs.

        cout << "Output file: " << fileName << endl;
        deleteFile(primo::ustring(fileName));

        primo::ref<MediaSocket> outSocket(Library::createMediaSocket());
        outSocket->pins()->add(inSocket->pins()->at(i));
        outSocket->setFile(primo::ustring(fileName));

        transcoder->outputs()->add(outSocket.get());

Run the Transcoder

After configuring the input and output sockets, the sample opens the transcoder, runs the demuxing operation, closes it, and returns the result.

bool demuxMP4(Options& opt)
{
    auto transcoder = configureTranscoder(opt);

    if (!transcoder)
        return false;

    if (!transcoder->open())
    {
        printError("Transcoder open", transcoder->error());
        return false;
    }

    if (!transcoder->run())
    {
        printError("Transcoder run", transcoder->error());
        return false;
    }

    transcoder->close();
    return true;
}

Complete C++ Code

Here’s the complete working example from demux_mp4_file.cpp:

#include <primo/avblocks/avb.h>

#include <primo/platform/reference++.h>
#include <primo/platform/error_facility.h>
#include <primo/platform/ustring.h>

#include "util.h"
#include "options.h"

#include <cstdio>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>

#include <memory.h>

using namespace primo::codecs;
using namespace primo::avblocks;
using namespace std;

primo::ref<Transcoder> configureTranscoder(Options& opt)
{
    primo::ref<MediaInfo> info(Library::createMediaInfo());
    info->inputs()->at(0)->setFile(primo::ustring(opt.inputFile));

    if (!info->open())
    {
        printError("MediaInfo open", info->error());
        return primo::ref<Transcoder>();
    }

    primo::ref<MediaSocket> inSocket(Library::createMediaSocket(info.get()));

    info->close();

    primo::ref<Transcoder> transcoder(Library::createTranscoder());
    transcoder->setAllowDemoMode(true);

    transcoder->inputs()->add(inSocket.get());

    bool audio = false;
    bool video = false;

    for (int i = 0; i < inSocket->pins()->count(); ++i)
    {
        string fileName;
        if (inSocket->pins()->at(i)->streamInfo()->mediaType() == MediaType::Audio && !audio)
        {
            audio = true;
            fileName = opt.outputFile + ".aud.mp4";
        }
        else if (inSocket->pins()->at(i)->streamInfo()->mediaType() == MediaType::Video && !video)
        {
            video = true;
            fileName = opt.outputFile + ".vid.mp4";
        }
        else
        {
            inSocket->pins()->at(i)->setConnection(PinConnection::Disabled);
            continue;
        }

        cout << "Output file: " << fileName << endl;
        deleteFile(primo::ustring(fileName));

        primo::ref<MediaSocket> outSocket(Library::createMediaSocket());
        outSocket->pins()->add(inSocket->pins()->at(i));
        outSocket->setFile(primo::ustring(fileName));

        transcoder->outputs()->add(outSocket.get());
    }

	return transcoder;
}

bool demuxMP4(Options& opt)
{
    auto transcoder = configureTranscoder(opt);

    if (!transcoder)
        return false;

    if (!transcoder->open())
    {
        printError("Transcoder open", transcoder->error());
        return false;
    }

    if (!transcoder->run())
    {
        printError("Transcoder run", transcoder->error());
        return false;
    }

    transcoder->close();
    return true;
}

int main(int argc, char* argv[])
{
	Options opt;

	switch(prepareOptions(opt, argc, argv))
	{
		case Command: return 0;
		case Error:	return 1;
        case Parsed: break;
	}

    Library::initialize();

	bool demuxResult = demuxMP4(opt);

    Library::shutdown();

	return demuxResult ? 0 : 1;
}

How to Run

See the build instructions for macOS and the demux_mp4_file example for details.

Command Line

demux_mp4_file --input <input_mp4_file> --output <output_mp4_file_name_without_extension>

Examples

List options:

./bin/x64/demux_mp4_file --help
Usage: demux_mp4_file -i <input_mp4_file> -o <output_mp4_file_name_without_extension>
  -h,    --help
  -i,    --input    Input mp4 file.
  -o,    --output   Output mp4 filename (without extension).

Extract the audio and video elementary streams from the input file ./assets/mov/big_buck_bunny_trailer.mp4 into output files named big_buck_bunny_trailer.vid.mp4 and big_buck_bunny_trailer.aud.mp4:

mkdir -p ./output/demux_mp4_file

./bin/x64/demux_mp4_file \
    --input ./assets/mov/big_buck_bunny_trailer.mp4 \
    --output ./output/demux_mp4_file/big_buck_bunny_trailer