This tutorial is created for academic purposes. Developed codec was part of my Master of Science thesis: Video compression in Linux .
First of all, you need to get source code of FFmpeg distribution. You can do this with git. Go to http://ffmpeg.org download site and choose clone URL. Then clone repository using git:
$ git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg
Cloning into 'ffmpeg'...
remote: Counting objects: 228734, done.
remote: Compressing objects: 100% (53705/53705), done.
remote: Total 228734 (delta 179855), reused 221914 (delta 174660)
Receiving objects: 100% (228734/228734), 58.45 MiB | 462 KiB/s, done.
Resolving deltas: 100% (179855/179855), done.
Before making any changes, create new local branch named msc-coder and switch to it.
$ git branch msc-coder
$ git checkout msc-coder
Switched to branch 'msc-coder'
$ git branch
master
* msc-coder
Before we dive into codec implementation specifics lets try building FFmpeg. Go to root directory of FFmpeg distribution and execute configure script:
$ ./configure
...
Enabled decoders:
aac cinepak loco
...
Enabled encoders:
a64multi h263p pgm
...
Enabled demuxers:
aac iff pcm_u24be
...
Enabled muxers:
a64 ipod pcm_mulaw
...
I have highlighted some important things in video codec development. First of all, we can see which encoders and decoders are currently enabled. Besides, we can also see enabled muxers/demuxers. Next steep is building with make. You can reduce build time by providing -j flag which distributes building process to multiple cores:
$ make -j5
After successful build we should get ffmpeg executable. Notice there is also ffmpeg_g which is compiled with debug symbols included. Check version of your build:
$ ./ffmpeg -version
ffmpeg version N-41414-g134d0f7
built on Jun 8 2012 16:48:32 with gcc 4.6.3
configuration:
libavutil 51. 56.100 / 51. 56.100
libavcodec 54. 25.100 / 54. 25.100
libavformat 54. 6.101 / 54. 6.101
libavdevice 54. 0.100 / 54. 0.100
libavfilter 2. 78.101 / 2. 78.101
libswscale 2. 1.100 / 2. 1.100
libswresample 0. 15.100 / 0. 15.100
Libavcodec library consists all audio/video codecs of FFmpeg. Developing new video codec requires knowing some basic structures. In this section I describe necessary structures which will be used to add new video codec.
typedef struct AVCodec {
const char *name;
const char *long_name;
enum AVMediaType type;
enum CodecId id;
int capabilities;
const AVRational *supported_framerates;
const enum PixelFormat *pix_fmts;
int priv_data_size;
int (*init)(AVCodecContext *);
int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame, int *got_packet_ptr);
int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);
int (*close)(AVCodecContext *);
}
Fields description:
typedef struct AVFrame {
uint8t *data[AV NUM DATA POINTERS];
int linesize[AV NUM DATA POINTERS];
int width ,height;
int key_frame;
int coded_picture_number;
int display_picture_number;
} AVFrame;
Fields description:
typedef struct AVPacket {
uint8t *data;
int size;
int flags;
int duration;
} AVPacket;
Fields description:
typedef struct AVCodecContext {
void * priv_data;
int compression_level;
int width, height;
enum PixelFormat pix_fmt;
int frame_number;
int frame_bits;
} AVCodecContext;
Fields description:
In this section you can find instruction on adding simple codec stub with name msc. To add new codec you have to follow these steps:
enum CodecID {
...
CODEC_ID_MSC = 0x30000,
...
};
#include "avcodec.h"
#include "internal.h"
#include "libavutil.h"
typedef struct MscDecoderContext {
// decoder context
} MscDecoderContext;
typedef struct MscEncoderContext {
// encoder context
} MscEncoderContext;
static int decode_init(AVCodecContext *avctx) {
av_log(avctx, AV_LOG_INFO, "MSC decode init\n");
return 0;
}
static int decode(AVCodecContext * avctx, void *outdata, int *outdata_size, AVPacket *avpkt) {
av_log(avctx, AV_LOG_INFO, "MSC decode\n");
return 0;
}
static int decode_close(AVCodecContext *avctx) {
av_log(avctx, AV_LOG_INFO, "MSC decode close\n");
return 0;
}
static int encode_init(AVCodecContext *avctx) {
av_log(avctx, AV_LOG_INFO, "MSC encode init\n");
return 0;
}
static int encode_frame(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame, int *got_packet_ptr) {
av_log(avctx, AV_LOG_INFO, "MSC encode\n");
return 0;
}
static int encode_close(AVCodecContext *avctx) {
av_log(avctx, AV_LOG_INFO, "MSC encode close\n");
return 0;
}
AVCodec ff_msc_decoder = {
.name = "msc",
.type = AVMEDIA_TYPE_VIDEO,
.id = CODEC_ID_MSC,
.priv_data_size = sizeof(MscDecoderContext),
.init = decode_init,
.close = decode_close,
.decode = decode,
.capabilities = CODEC_CAP_DR1,
.long_name = NULL_IF_CONFIG_SMALL("Msc coder"),
};
AVCodec ff_msc_encoder = {
.name = "msc",
.type = AVMEDIA_TYPE_VIDEO,
.id = CODEC_ID_MSC,
.priv_data_size = sizeof(MscEncoderContext),
.init = encode_init,
.close = encode_close,
.encode2 = encode_frame,
.pix_fmts = (const enum PixelFormat[]) {PIX_FMT_YUV420P, PIX_FMT_NONE},
.long_name = NULL_IF_CONFIG_SMALL("Msc coder"),
};
void avcodec_register_all(void)
{
...
REGISTER_ENCDEC (MSC, msc);
...
}
# decoders/encoders/hardware accelerators
...
OBJS-$(CONFIG_MSC_DECODER) += msccoder.o
OBJS-$(CONFIG_MSC_ENCODER) += msccoder.o
...
libavcodec/allcodecs.c: In function ‘avcodec_register_all’:
libavcodec/allcodecs.c:263:1: error: ‘CONFIG_MSC_ENCODER’ undeclared (first use in this function)
libavcodec/allcodecs.c:263:1: note: each undeclared identifier is reported only once for each function it appears in
libavcodec/allcodecs.c:263:1: error: ‘CONFIG_MSC_DECODER’ undeclared (first use in this function)
Values CONFIG_MSC_ENCODER and CONFIG_MSC_DECODER are created by
macro REGISTER_ENCDEC. In root directory executed configure script
and build. Added codec should be visible as enabled in configure
script output.
$ ./configure
...
Enabled decoders:
...
adpcm_sbpro_2 exr msc
...
Enabled encoders:
...
adpcm_ima_wav msc ra_144
...
$ make -j5
...
CC libavcodec/msccoder.o
...
$ ./ffmpeg -codecs
...
Codecs:
D...... = Decoding supported
.E..... = Encoding supported
..V.... = Video codec
..A.... = Audio codec
..S.... = Subtitle codec
...S... = Supports draw_horiz_band
....D.. = Supports direct rendering method 1
.....T. = Supports weird frame truncation
......F = Supports frame-based multi-threading
......S = Supports slice-based multi-threading
......B = Supports both frame-based and slice-based multi-threading
-------
...
DEV D msc Msc coder
...
There are variety of existing audio/video containers in FFmpeg. You can write your own if you want. I used existing open source matroska container format. To add MSC codec as video codec to mkv container you need simply to add this line to libavformat/matroska.c file:
const CodecTags ff_mkv_codec_tags[]={
...
{"V_MSC" , CODEC_ID_MSC},
...
};
Then if you use msc coder with mkv container information about used codec will be automatically persisted. You can test it by simply compress video sequence and then check if ffmpeg is able to detect used codec by ffprobe utility.
$ ffmpeg -i resources/bowing_qcif.avi -vcodec msc results/bowing_qcif.mkv
...
Output #0, matroska, to 'results/bowing_qcif.mkv':
Metadata:
encoder : Lavf54.6.101
Stream #0:0: Video: msc, yuv420p, 176x144 [SAR 128:117 DAR 1408:1053], q=2-31, 200 kb/s, 1k tbn, 29.97 tbc
Stream mapping:
Stream #0:0 -> #0:0 (rawvideo -> msc)
...
$ ffprobe results/bowing_qcif.mkv
...
Input #0, matroska,webm, from 'results/bowing_qcif.mkv':
Metadata:
ENCODER : Lavf54.6.101
Duration: 00:00:10.01, start: 0.000000, bitrate: 7821 kb/s
Stream #0:0: Video: msc, yuv420p, 176x144, SAR 12:11 DAR 4:3, 29.97 fps, 29.97 tbr, 1k tbn, 1k tbc (default)
...
FFmpeg is used in various applications. Some of them use static linking with specific version of ffmpeg or even maintain their own repo for ffmpeg. Thus, best way to add new codec to them, is to prepare patch and then apply it. In following sections I am going to describe integration with MPlayer which uses static linking and integration with gstreamer-ffmpeg plugin which uses dynamic linking.
But first, lets create patch. If you followed my recommendation to create branch to work with new codec, you can use git diff option to create patch. Firstly commit all your current work to branch. Then you can crate msc_coder.patch file with redirect. You should be in root directory of ffmpeg.
$ git diff master..msc-coder > msc_coder.patch
To apply patch you simply invoke patch command with -p1 argument.
$ patch -p1 < msc_coder.patch
MPlayer uses static linking with FFmpeg libraries. You can invoke ./configure script which pulls new ffmpeg snapshot from git repository. Then you have to apply patches to add new codec. You can also create symbolic link with name ffmpeg in root directory of MPlayer distribution (Note that this solutions is not always feasible because of mismatch between version of ffmpeg that you have and version needed by mplayer).
To configure etc/codecs.conf you have to follow instructions from DOCS/tech/codecs.conf.txt of MPlayer source code distribution. In case of msc coder simply add these lines in video codecs section.
videocodec ffmsc
info "FFmpeg MSC coder"
status working
fourcc MSC0 ; internal MPlayer FourCC
driver ffmpeg
dll msc
out I420
You should also define internal MPlayer FourCC for added codec in file libmpdemux/mp_taglists.c.
...
static const struct AVCodecTag mp_codecid_override_tags[] = {
...
{ CODEC_ID_MSC, MKTAG('M', 'S', 'C', '0')},
...
After applying these changes you can build mplayer and install it. On Ubuntu 12.04 xorg-dev package is required for proper building of self compiled mplayer.
$ ./configure
$ make -j5
$ sudo make install
$ mplayer results/bowing_qcif.mkv
Ubuntu 12.04 gstreamer-ffmpeg plugin uses fork of ffmpeg - libav. By the time of writing, differences between ffmpeg and libav were so small, that patching libav with patch prepared for ffmpeg caused no problems. GStreamer is used by various applications including default video movie player for GNOME - Totem or thumbnails preview in Nautilius file manager. I am going to describe steps necessary to get thumbnails preview in nautilius file manager using ffmpegthumbnailer.
By default nautilius is using totem-video-thumbnailer as a video thumbnailer in GNOME desktop environment. To make it work with ffmpeg you can use gstreamer-ffmpeg plugin and ffmpegthumbnailer. In Ubuntu gstreamer-ffmpeg package is compiled with system provided version of libav. So first step is to patch libav for whole system and install it. On Ubuntu you can download source packages with apt-get command with source argument.
$ sudo apt-get source libav-source
You have to pull all required build-dependent packages before building deb package. You can do this with apt-get build-dep command.
$ sudo apt-get build-dep libav-source
You should get directory libav-VERSION with libav framework. As mentioned before you can try to simply patch source with patch created for ffmpeg. Note that deb source packages provide their own patches in .pc/ directory and they can interfere with your patches. Please refer to Ubuntu Packaging Guide for more information how you should apply your own patches. To build binary package use dpkg-buildpackage commnand within libav-VERSION directory.
$ dpkg-buildpackage -rfakeroot -uc -b
After successful build you should get deb packages in parent directory. Install them with dpkg command.
$ ls *.deb
ffmpeg_0.8.3-0ubuntu0.12.04.1_all.deb libavdevice-dev_0.8.3-0ubuntu0.12.04.1_i386.deb libav-tools_0.8.3-0ubuntu0.12.04.1_i386.deb
ffmpeg-dbg_0.8.3-0ubuntu0.12.04.1_i386.deb libav-doc_0.8.3-0ubuntu0.12.04.1_all.deb libavutil51_0.8.3-0ubuntu0.12.04.1_i386.deb
ffmpeg-doc_0.8.3-0ubuntu0.12.04.1_all.deb libavfilter2_0.8.3-0ubuntu0.12.04.1_i386.deb libavutil-dev_0.8.3-0ubuntu0.12.04.1_i386.deb
libavcodec53_0.8.3-0ubuntu0.12.04.1_i386.deb libavfilter-dev_0.8.3-0ubuntu0.12.04.1_i386.deb libpostproc52_0.8.3-0ubuntu0.12.04.1_i386.deb
libavcodec-dev_0.8.3-0ubuntu0.12.04.1_i386.deb libavformat53_0.8.3-0ubuntu0.12.04.1_i386.deb libpostproc-dev_0.8.3-0ubuntu0.12.04.1_i386.deb
libav-dbg_0.8.3-0ubuntu0.12.04.1_i386.deb libavformat-dev_0.8.3-0ubuntu0.12.04.1_i386.deb libswscale2_0.8.3-0ubuntu0.12.04.1_i386.deb
libavdevice53_0.8.3-0ubuntu0.12.04.1_i386.deb libav-source_0.8.3-0ubuntu0.12.04.1_all.deb libswscale-dev_0.8.3-0ubuntu0.12.04.1_i386.deb
$ sudo dpkg --install *.deb
Then you can check that we are using libav ffmpeg fork (look for line containing information Copyright (c) 2000-2012 the Libav developers).
$ ffmpeg -version
ffmpeg version 0.8.3-4:0.8.3-0ubuntu0.12.04.1, Copyright (c) 2000-2012 the Libav developers
built on Jul 15 2012 18:19:18 with gcc 4.6.3
*** THIS PROGRAM IS DEPRECATED ***
This program is only provided for compatibility and will be removed in a future release. Please use avconv instead.
ffmpeg 0.8.3-4:0.8.3-0ubuntu0.12.04.1
libavutil 51. 22. 1 / 51. 22. 1
libavcodec 53. 35. 0 / 53. 35. 0
libavformat 53. 21. 0 / 53. 21. 0
libavdevice 53. 2. 0 / 53. 2. 0
libavfilter 2. 15. 0 / 2. 15. 0
libswscale 2. 1. 0 / 2. 1. 0
libpostproc 52. 0. 0 / 52. 0. 0
We are now ready to build gstreamer-ffmpeg plugin. As earlier we are going to create patched .deb package version. You can find information about patching policy for this project in HACKING file from git source version. Let's firstly download source package and build dependencies.
$ sudo apt-get source gstreamer0.10-ffmpeg
$ sudo apt-get build-dep gstreamer0.10-ffmpeg
To integrate our new codec with gstreamer we have to modify gst_ffmpeg_codecid_to_caps function ext/ffmpeg/gstffmpegcodecmap.c file. It's purpose is to convert provided codec_id and AVCodecContext to GstCaps caps structure.
GstCaps *
gst_ffmpeg_codecid_to_caps (enum CodecID codec_id,
AVCodecContext * context, gboolean encode)
{
...
switch (codec_id) {
...
case CODEC_ID_MSC:
caps = gst_ff_vid_caps_new (context, codec_id, "video/x-msc", NULL);
break;
...
After applying specified patch we can build and install gstreamer0.10-ffmpeg package. Our codec should be visible in output of gst-inspect ffmpeg command.
$ gst-inspect ffmpeg
...
ffdec_msc: FFmpeg Msc coder decoder
...
Now we are ready to replace totem-video-thumbnailer with ffmpegthumbnailer. Firstly install ffmpegthumbnailer package.
$ sudo apt-get install ffmpegthumbnailer
Then you have to replace following contents of /usr/share/thumbnailers/totem.thumbnailer:
[Thumbnailer Entry]
TryExec=/usr/bin/totem-video-thumbnailer
Exec=/usr/bin/totem-video-thumbnailer -s %s %u %o
MimeType=application/mxf;application/ogg;...
with this (note usage of video/x-msc mime type used in ext/ffmpeg/gstffmpegcodecmap.c file):
[Thumbnailer Entry]
TryExec=ffmpegthumbnailer
Exec=ffmpegthumbnailer -s %s -i %i -o %o -c png -f -t 10
MimeType=application/mxf;application/ogg;...;video/x-msc;
Then just quit nautilius and clear thumbnails cache
nautilus -q
rm ~/.thumbnails/fail/gnome-thumbnail-factory/*
rm ~/.thumbnails/normal/*
Now you should get thumbnail preview of video coded by msc-coder codec in mkv container.
$ pwd
~/private/msc/msc-coder/results
$ ffprobe akiyo_qcif.mkv
...
Stream #0.0: Video: msc, yuv420p, 176x144, PAR 12:11 DAR 4:3, 29.97 fps, 29.97 tbr, 1k tbn, 1k tbc (default)
...