Building ffmpeg: a rather long, sometimes humorous, and cheerfully detailed problem solving guide to compiling open-source software on Mac OSX

Preamble

Have you always yearned for the courage and skill required to build your own open-source binaries from source tarballs? Are you tired of hanging on the mercy of more adept computer geeks to compile your open-source software for you? Do you have a burning need to compile something despite having no idea what a segfault is?

Then this tutorial is for you!

Welcome to a detailed guide on how to download, compile, and install open-source libraries and code. Specifically, we will be installing the ffmpeg library and its associated dependencies, after which we will use this library to compile and run a simple video player: “An ffmpeg and SDL Tutorial – How to Write a Video Player in Less than 1000 lines of Code,” available at Dranger.com.

Because the scope of this tutorial is rather general and detailed, if you are already reasonably comfortable installing software from source and are mostly just interested in installing ffmpeg, I would point you toward another, briefer, tutorial – Installing and Using FFmpeg on Mac OSX – that is available on Stephen Jungles’s site.

Table of contents

Introduction:

A good text editor
OSX Terminal
Methods of distribution
What is ffmpeg?

Installing the dependencies:

Installing dependencies
LAME MP3 Encoder/Decoder
Our first problem…
FAAD2 – an AAC Decoder
Bootstrapping
FAAC – an AAC Encoder

Installing FFmpeg:

Installing FFmpeg itself
Configuring ffmpeg again

Tutorial01 from Dranger.com:

One last dependency – SDL
Start Tutorial01.c from Dranger.com
Problems with spaces in names
Finding files – find and locate
Fixing PATH
Headers found, PATH fixed: compile tutorial01.c again
Updating img_convert to sws_scale – an intro to grep!
Tutorial01.c – more undefined symbols
Tutorial01.c – Holy…uh, yeah. Success!

I’m a bad, bad man:

A confession

Tutorial08.c from Dranger.com, a.k.a. ALMOST THERE

Tutorial08.c – the video player
pstrcpy – symbol not found
Smacked by Satan
A drumroll, please…

Just can’t resist:

One last joke

Preamble, part 2

Installing software from the source is potentially as simple as downloading the files, unpacking them, and then running:

./configure
make
sudo make install

But sometimes it’s not that simple (as evinced by the length of this guide). It’s like the human body: a billion-billion things can go wrong. In fact, it’s a wonder that it works at all. This tutorial is a “problem solving” guide because as I move through the process of installing and using the source I will detail some of the challenges (read: mistakes) I faced and show the methods I used to overcome them.

So what tools do we need?

First off, we’ll need a good text editor.

Mac’s TextEdit is OK–actually, for programming it’s crap. It can be made to work (you have to set the file type to plain text in Format menu). Regardless, I would highly suggest using something a little better.

There are many options, but I use BBEdit. What does spending $125 on a text editor get you? Lots of nifty features that if you write code you care deeply about:

  • syntax highlighting
  • auto-completion
  • opening hidden files
  • easy access to multiple files
  • editing files via ftp
  • and perhaps most importantly, matching brackets and parenthesis (which can be a HUGE help when trying to understand someone else’s messy html documents, or tracking down a misplaced bracket somewhere).

But if you don’t want to spend $125, just download TextWrangler, their free version. It doesn’t have all the nifty features, but it’s still pretty damn good.

Alternatively, you can also use XCode, which has many of the same features. This brings up another point: you’ll need to install Apple’s Developer Tools, if you haven’t already done so. It’s available on the OSX install CD, but the most recent update can be downloaded from the the Apple developer site.

Terminal

If you’ve never used the terminal interface in OSX, it can be a little intimidating, not the least because you can completely screw up your machine in obscure and esoteric ways that will cause the geniuses at the Apple store to recoil in panic and knowingly suggest, “You send you computer in for further testing, or, at least, reinstall your operating system. And I notice you haven’t upgraded to OS 10.9 Super Ferret Monkey. Why don’t you view this as the perfect opportunity to do so? And do you already own an iPhone? What about an iPad?”

SO BE CAREFUL.

An aside!
This is an aside (actually it’s more of a rant). I’ll be sprinkling these in to expand upon topics brought up during the main flow of the tutorial, which, in this case, is the Genius Bar. The name’s obnoxious.

Yes, sigh (as I slump at the counter). I already tried removing the battery and rebooting.

I swear they give them a card that says: reboot the machine, reset the PRAM, and try and start in target-disk mode, and if that doesn’t work, send it in.

I’m not really that bitter about it, I just don’t think they should call them geniuses unless they know their…uh, I just don’t think they should call it the Genius Bar. But the last time I spoke to a tech support rep. that was actually of help to me was circa 1989: the computer was a 386, I was 12, and the tech support representative on the other end of the phone introduced me to the CMOS and BIOS menus.

If you’ve absolutely never used the terminal, it might be worth your time to read through a few tutorials on this subject before you begin here.

Terminal is picky: file names and commands are case sensitive. Terminal rewards proficiency and scorns the novice–it’s like the opposite of OSX. It’s the grumpy old unix admin hidden inside the candy coated shell of your mac.

Some of the basic commands we’ll be using are:

  • pwd – display your current location in the file system
  • cd – change directories. For examples, cd .. changes the current directory to the parent directory.
  • ls – list the contents of the current directory. ls -l also displays the permissions, size, and date stamps of the contents of the directory.
  • rm – remove a file. This will permanently delete the file, not send it to trash. So again, be careful.
  • mv – moves or renames a file. mv myfile.mp3 jazz/latemiles will move the file “myfile.mp3” to the subdirectory “jazz/latemiles.”
  • mkdir – creates a directory. mkdir anewdir creates a new directory named “anewdir”
  • rmdir – deletes a directory. rmdir anewdir deletes the directory named “anewdir”, as long as it is empty
  • rm -r – deletes a directory and its contents. rmdir -r anewdir deletes the directory named “anewdir” and all of the files contained within. Be careful with this, especially when using wildcards. The grump old unix admin will quite happily delete your entire system drive if you ask him to.
  • cp – copies a file.
  • find – searches the directory structure for a file.
  • locate – like find, is used to search for a file, but instead of searching the file structure uses a database to search for files. May be faster, but if the file is new, it might not be in the database.
  • whereis – checks standard binary directories for the specified program file; does not search user paths
  • which – similar to whereis, but also searches directories listed in the shell’s PATH variable.
  • grep – used to search the contents of files. Incredibly useful. We’ll definitely be getting back to this one.

If you’re having trouble understanding how to use a command properly, just for fun, try adding the --help option after it. For example, type grep --help into the terminal. This will cause the terminal to spit out a long list of possible options, which, if you already have a pretty good idea of what you’re doing, are very helpful. However, if you’re just getting started it’s probably going to be a little overwhelming.

The --help help flag doesn’t work with all of the commands. Another option is to read the manual pages for the command by typing man COMMAND, such as, man grep or, man ls. But the man pages suffer from the same problem as the help option: too much information.

So if you’re really new to this (i.e., if this “Usage: grep [OPTION]… PATTERN [FILE] …” means nothing): google is your friend.

The PATH variable

When you open a terminal window, a whole set of configuration routines are run that set up the environment in which the shell runs, including loading the PATH variable. Among other things, the PATH variable determines the search directories for library files, user-installed commands, and header includes.

Type $PATH into a terminal window and it will show you the contents of your PATH (the dollar sign tells the shell that the following word is a variable).

As we will see later, not having the right directories in your PATH can cause the compiler all sorts of problems.

Getting the source

There are a ton of ways that open-source software is distributed: CVS, Git, SVN, mac-ports, fink, and plain old downloading.

Git, CVS, and SVN are version control systems that allow multiple developers to work on the same project from different places without completely screwing things up. Patches (changes) to the source can be uploaded to the source repository and integrated with the work of other developers, who can stay up to date by synchronizing the code their working on with the most recent version.

Most likely this won’t matter to you, unless you plan on adding to the development of the code base. However, these systems can also be used to download a copy of the code to your local computer, and fortunately, most websites that use this distribution method include the terminal command to download the most recent version on their web page. Just copy the command into the terminal and go.

MacPorts and Fink are two great projects that attempt to greatly simplify the distribution of open-source software on OSX. We won’t in this tutorial explore them, but there’s a ton of great open-source software out there, and they’re worth looking into if you really want to dive in.

Quite often, I just download the source from the project’s home page. This is often a good idea because it is easier to see the development history of the project and perhaps choose a version that is a little older, and thus more stable.

Don’t get me wrong…

I like my Mac a lot, so don’t get the wrong idea from my earlier rant. It’s stable, has taken a beating of heavy use over the last four years and has stood up reasonably well, it’s stable, the OS provides a nice mix of slick ease of use and lower-level nuts and bolts, and it’s stable. Did I mention it’s stable? It’s my first Mac, and Mac OSX isn’t perfect, but I’ve spent almost no time troubleshooting the operating system and I’ve only reinstalled the OS once. After the nightmare that was Windows 98, I feel like that’s a pretty awesome record (that Vista didn’t appear to do much to top; although, XP seemed OK; so maybe Windows is like vampires and werewolves: it skips a generation).

My concern about Apple is that in their meteoric transformation from a niche computer company supported mainly by educational institutions and media professionals to a consumer electronics company selling iPods and iPhones, they’re gonna forget about the “serious” users and force me to switch to Linux, which is starting to look better and better with each $200 upgrade for Logic Pro (8.02 guys? Seriously? That’s as far as you could go before you jumped a full version? Aren’t we reporting record-breaking profits?).

OK, so maybe I am a little bitter, but not bitter enough to overcome the PTSD that Windows burdened me with. Sometimes I catch myself twitching involuntarily “control-s control-s,” so ingrained in me was the habit to save every few seconds, and even then I’d lose work.

On to ffmpeg!

So what is ffmpeg? The short of it is that it can be used to play and convert media files.

The long of it is that it’s a library for reading, writing, transcoding, muxing, and demuxing audio and video files. There is a bewildering array of multimedia formats around these days. And to make things even more confusing, files have both a container and a encoding format, sometimes carrying the same name, sometimes not. For example, MP3 is an encoding format: it specifies how the raw sound data is compressed and stored. It is also a file format. Even more confusingly, MP3 stands for MPEG-1 Audio Layer 3, and is not to be confused with MPEG-3, which was a HD video format that turned out to be unnecessary and was rolled into a separate profile of the MPEG-2 standard.

MPEG, incidentally, is the Motion Picture Experts Group, whose primary mission it seems is to create an increasingly bewildering array of nearly identical acronyms, perhaps in an attempt to confuse media pirates.

Another example is Apple’s Quicktime, which is a container format. It specifies how various media streams are contained, but not encoded. Quicktime files can contain streams in any one of dozens of various encoding formats, including MP3 and MPEG-2, as well as AAC, WAV, MPEG-4, DV, H.264, etc…

So what ffmpeg does is mux and demux container formats, i.e. separate them into the component streams or package streams into a single file, and read and write the streams according to the specifications of particular codecs, which are the standards used to encode and sometimes compress the raw audio-visual data.

The goal of this tutorial is to use this library and code downloaded from Stephen Dranger’s site to compile a working video player.

So why was I interested in it? Did I have a vast library of audio-visual data that needed muxing? Not really, I just thought it would be fun to play with.

Install the dependencies

For this part of the tutorial, we’re going to follow Stephen Jungles’ suggestions and install a few libraries upon which ffmpeg will depend before we install ffmpeg itself. If you don’t care about ffmpeg being able to read or write MP3 or AAC audio files, then this isn’t necessary. However, as these are two of the most popular audio codecs, I decided to go ahead and install them.

First off, we should create a directory for the various source libraries we are going to download and build. I would suggest something near the root directory and not on the desktop or in the user documents folder because typing /Developer/Downloaded\ Libraries is quicker than /Users/andrewhughes/Documents/Downloaded\ Libraries, and you’ll probably be typing this a lot.

Terminal and spaces
The unix-style shell does not like spaces in names. It interprets them as a break in the token. For example, if I typed cd /Developer/Downloaded Libraries, I would most likely get a “File not found” error because the shell would only see cd /Developer/Downloaded, which doesn’t exist. There are three ways to deal with this. 1) Avoid using names with spaces. 2) “Escape” the space with a backslash (as I did above). 3) Enclose the name in quotes.
Tab = Autocomplete
If you press the tab key while you are typing in the terminal, the shell will try and complete the word for you. It’s a pretty awesome function. Try it. If nothing happens, most likely there are multiple possibilities for the text you have entered, in which case, hit tab twice and the shell will list the possibilities.

LAME MP3 Encoder

The LAME MP3 encoder is an mp3 encoding and decoding library released under the LGPL license.

To get the source, either follow this link to the SourceForge home page or use CVS. Note: if you use the CVS commands, they will place the download source in a “lame” subdirectory relative to your current directory, so cd to the “Downloaded Libraries” directory before you enter the CVS commands. If you don’t, it’s not the end of the world, you’ll just have to copy or move it. Actually, it doesn’t really matter where you compile the code at all, except that I like to keep all of the source for the libraries and programs I have downloaded in the same spot.

Here are the CVS commands (it’s two separate commands, and when it asks for a password, just hit enter):

cvs -d:pserver:anonymous@lame.cvs.sourceforge.net:/cvsroot/lame login
cvs -z3 -d:pserver:anonymous@lame.cvs.sourceforge.net:/cvsroot/lame co -P lame

If you don’t use CVS and instead download the source “tarball” (a compression format, like zip), expand the package and copy the uncompressed files to your “Downloaded Libraries” directory.

The next step is to open a terminal window and cd to the source directory. On my computer that would be:

cd /Developer/Downloaded\ Libraries/lame

Absolute and relative paths

Notice that the command above started with a slash. This told the terminal that I was specifying an absolute path, meaning that I could have typed that command into the terminal and regardless of where I was in the file structure at that moment I would have been transported to the proper directory. It’s a bit like specifying someone’s full name and social security number.

If I had typed cd Developer/Downloaded\ Libraries/lame, the same command without the leading slash, a relative path, the terminal would have looked for a subdirectory in my current directory called “Developer”, and if I wasn’t already in the root path, it would have told me: “No such file or directory.”

It’s useful to know that cd / will always take you to the root directory of your file system, and cd .. will take you to the parent directory.

In contrast, it’s not useful at all to know that cd . will take you absolutely nowhere except where you already are: because wherever you go, there you are!

OK. So now that we’re in the source directory, let’s get to it. Almost all of these source packages are designed to be installed using the same three commands:

./configure
make
sudo make install

The first command, ./configure, runs a script that configures the makefile for your operating system and computer.

Why the dot-slash?
The dot-slash is required to run the script because the shell only searches directories in the PATH variable for files to execute, and more than likely the current directory is not. Because of this, the full, absolute path is required to run the script; fortunately, however, the dot expands to the full path of the current directory, so typing “./” is the same as typing the full path to the current working directory.

A makefile is another script that orchestrates the complex process of compiling and linking the source into an executable file or a library. The second command, make, runs the now-configured makefile. If you’re lucky, a whole lot of text wills scroll by while gcc compiles and links the program. If you’re really lucky, the process will end with a message saying something like “Nothing to be done for blah.” If you receive an error message–as I did, which we’ll get to in a minute–it’s time to troubleshoot.

The third command, sudo make install, runs another script that copies the newly created libraries into the appropriate paths on your computer. sudo is a special command that temporarily gives the script permission to access protected parts of your file system, and you’ll have to type in your password.

Our first problem…

When I tried to compile the code, I received the following error:

ar: /Developer/Downloaded: No such file or directory
make[3]: *** [libmp3lame.la] Error 1
make[2]: *** [all-recursive] Error 1
make[1]: *** [all-recursive] Error 1
make: *** [all] Error 2

Fortunately, this was a rather trivial error. The clue is in the first line above, “/Developer/Downloaded: No such file or directory”. Remember that my full path was “/Developer/Downloaded Libraries/lame”, which brings us back to unix-style shells not liking spaces in file and directory names.

In this case, the simplest solution was to change the name of the directory to “Downloaded_Libraries”, using an underscore instead of a space.

After typing ./configure and make again, the library compiled, and I used sudo make install to install the files.

FAAD2 Library

FAAD2 is an MPEG-4 and MPEG-2 AAC decoder that is released under the GPLv2 license. Including this will allow ffmpeg to decode AAC audio.

The instructions on Stephen Jungle’s site use curl to download the source from the SourceForge website directly from the terminal. This is a perfectly legitimate way to fetch the source–except that if you copy and paste the command as written, you are going to download version 2.6.2 and the library is, at the time of this post, at 2.7.

Because of this, I decided to go to the AudioCoding.com website and download the most recent version of the library. They offer three versions of the FAAD2 library: two “bootstrapped” versions and a zipped version. Download either of the bootstrapped versions, uncompress them, and copy the files to your “Downloaded Libraries” directory.

INSTALL and README

It’s always a good idea to read through the documentation distributed with the libraries or programs. Most have a README file and possibly an INSTALL file that might contain operating system-specific instructions on installation and use.

From the terminal, type less README or less INSTALL and this will allow you to read through the file, scrolling with the up and down arrow keys, or paging down with the space bar. To quit, press “q”. Alternatively, you can type open FILE, with file being the file to be opened, and this is equivalent to having double clicked on the file in Finder. It will be opened with whatever program the system thinks is appropriate.

Another neat trick is to type open FILE &. The ampersand at the end tells the terminal to open the file as another process so that you can keep the file open and still use your original terminal window.

Now, in terminal, cd to the FAAD2 library directory and run the following:

./configure
make
sudo make install

With any luck, it will compile successfully for you as it did for me. Note that you can typically ignore compiler warnings. I haven’t compiled a bit of downloaded code yet that didn’t spit out a bunch of warnings–errors, however, are another story.

The last bit of my terminal output looked like this (the “nothing to be done for…” is a good sign):

main.c:798: warning: pointer targets in passing argument 4 of 'mp4ff_get_decoder_config' differ in signedness
main.c:888: warning: pointer targets in passing argument 5 of 'mp4ff_read_sample' differ in signedness
if gcc -DHAVE_CONFIG_H -I. -I. -I.. -I../include -I../common/faad -I../common/mp4ff    -g -O2 -MT audio.o -MD -MP -MF ".deps/audio.Tpo" -c -o audio.o audio.c; 
then mv -f ".deps/audio.Tpo" ".deps/audio.Po"; else rm -f ".deps/audio.Tpo"; exit 1; fi
if gcc -DHAVE_CONFIG_H -I. -I. -I.. -I../include -I../common/faad -I../common/mp4ff    -g -O2 -MT getopt.o -MD -MP -MF ".deps/getopt.Tpo" -c -o getopt.o `test -f '../common/faad/getopt.c' || echo './'`../common/faad/getopt.c; 
then mv -f ".deps/getopt.Tpo" ".deps/getopt.Po"; else rm -f ".deps/getopt.Tpo"; exit 1; fi
/bin/sh ../libtool --tag=CC --mode=link gcc  -g -O2   -o faad  main.o audio.o getopt.o ../libfaad/libfaad.la ../common/mp4ff/libmp4ff.a
mkdir .libs
gcc -g -O2 -o .libs/faad main.o audio.o getopt.o  ../libfaad/.libs/libfaad.dylib -lm ../common/mp4ff/libmp4ff.a
creating faad
Making all in plugins
make[3]: Nothing to be done for `all-am'.
make[2]: Nothing to be done for `all-am'.

Bootstrapping

The reason I said to download the bootstrapped version of the FAAD2 library is that the zipped version requires that you run the bootstrap script to generate the config files. Unfortunately, ./bootstrap didn’t work. Initially I was stumped. Then I found this page on the RoRCraft blog, which explains the problem about a third of the way down. Basically you have to strip the Windows carriage returns, which our shell doesn’t like, from the bootstrap file using the following code (shamelessly copied):

tr -d '\015' < bootstrap > bootstrapa
mv bootstrapa bootstrap
chmod +x bootstrap

Looking back, I realize that I’d missed a hint. When I tried to run the bootstrap script, I received the following error:

-bash: ./bootstrap: /bin/sh^M: bad interpreter: No such file or directory

“^M” is the windows control character for carriage returns.

FAAC Library

Where the FAAD2 library decodes AAC audio, this library encodes AAC audio. The installation steps are the same as for FAAD2. Go to the website, download the bootstrapped source package, decompress it, copy it to your “Downloaded Libraries” folder, and then run the now-familiar commands:

./configure
make
sudo make install

A warning!
This has nothing to do with the current section, but I just wanted to point out that sometimes you have to be careful copying and pasting code from the internet. The character encodings can get you, especially with quotes. If a copied bit of code isn’t working, check to make sure that any quotes are straight quotes and not “smart” quotes that curl inward. You can fix this by pasting the code into your trusty text editor and converting the text to ascii text.

Install FFmpeg, finally!

We’re going to use SVN to download the latest version of ffmpeg. In the terminal, cd to your “Downloaded Libraries” directory and enter the following:

svn checkout svn://svn.ffmpeg.org/ffmpeg/trunk ffmpeg

This will make a directory called ffmpeg. Go ahead and cd into that directory, but don’t run the usual commands just yet. This time we are going to pass some options to the configure script. Taken from Stephen Jungle’s page, enter the following into your terminal shell:

./configure --enable-libmp3lame --enable-shared --disable-mmx --arch=x86_64 --enable-libfaad --enable-libfaac

What do all the options mean?

  • --enable-libmp3lame: enables mp3 support
  • --enable-shared: creates a shared library (that we’ll need later to compile the video player program)
  • --disable-mmx: disables a type of processor-specific optimization that may not be compatible with Intel Macs
  • --arch=x86_64: specifies the processor architecture
  • --enable-libfaad --enable-libfaac: tell the script to enable AAC encoding and decoding support

Uh, oh. An Error.

Unknown option "--enable-libfaad".
See ./configure --help for available options.

This is what I got when I tried to run it. This of course doesn’t make any sense because Stephen Jungle’s tutorial specified this flag and we installed the FAAD library. So why isn’t it supported? When I typed ./configure --help a whole mess of configuration options were listed. FAAC support was listed, but not FAAD support.

After some googling, searching the ffmpeg mailing lists, and searching the project website, I finally found a page that said that ffmpeg natively supports AAC decoding. This means that the FAAD library is no longer necessary. So we can simply leave off that configuration option.

Open-source software a moving target
One of the things that makes working with open-source software tricky is that it’s a moving target. Tutorials on the internet will often reference older versions, and things may have changed. Use the project mailing lists, the FAQs, and good old internet searches, often copying the exact error string, to try and discover what’s going on.

FFmpeg, configure again…and finally make

Our new configure command is:

./configure --enable-libmp3lame --enable-shared --disable-mmx --arch=x86_64 --enable-libfaac

This time the configure script tells us: libfaac is nonfree and --enable-nonfree is not specified. We have to enable “nonfree” because the something in the FAAC library license prohibits us from redistributing the code. As I have no plans to develop a commercial product from this, I just went ahead and added the flag.

./configure --enable-libmp3lame --enable-shared --disable-mmx --arch=x86_64 --enable-libfaac --enable-nonfree

Ah…this finally worked for me. No errors.

Time for the last two steps of the same old dance. This may take quite a while, and that’s OK as long as it’s still churning out text and it hasn’t stopped with an error message.

make
sudo make install

What’s all that text mean?

So what is all that text rolling by in your terminal window? That’s your computer compiling the source files into object files and linking the object files into the final product. Files ending in “.c” or “.h” are source files. They are text files written in (relatively) human-friendly programming languages. The compiler translates these files into machine language to create the object files, which end with “.o”. Linking, the step after compiling, is the process of stitching all of these object files into a final executable or library.

Imagine that you’re wandering through the store and you see a box for a beautiful Leggo starship. You buy it and rush home, wondering how they could have gotten such a huge starship inside the modestly sized box. When you open the box you find several hundred tiny instruction manuals and not a single Leggo. Argh! Sigh. Each manual describes how to fabricate a different type of Leggo. They also specify how that particular Leggo relates to the hundreds of other Leggos. So, undaunted, desperately yearning for your starship, you set about fabricating all the necessary pieces. Whir, saw, lathe, cut, pour, etc… Several years later, penniless, divorced, dirty, unshaven, you finally finish all the pieces, and in a frenzied rush, you assemble the pieces into your starship, after which you bask in its glory–and curse when you realize that it will not carry you away into space.

The individual instruction manuals on how to fabricate the pieces are like the source code: they tell the computer how to build the object files. And analogous to the Leggos, the object files are the building blocks of the final program. The last step, assembling the starship, is like linking: the final phase where everything is brought together.

So, even if your computer isn’t building a starship, it’s doing a whole hell of a lot of work while you wait, so just be glad that it’s not going to end up penniless and dirty for it’s efforts. You might even give it a pat on the back when it’s done.

On to the video player! But first, one last dependency to install. Sigh.

Hopefully at this point you still remember the original goal of this tutorial: we’re going to compile a video player taken from a tutorial on Stephen Dranger’s page.

His code relies on another library besides ffmpeg. The library is called SDL, Simple Directmedia Layer, and is a cross-platform multimedia library that the program uses to take care of the windowing and actual display of the video files (because ffmpeg only decodes and encodes them).

Go to the download page, download the source (not the runtime libraries or the development libraries), unpack, and copy to your “Downloaded Libraries” directory.

If you notice, this package has a “README.MacOSX” file. We should check this out, so less README.MacOSX to read through it.

Looks like we can just use the standard commands, so go ahead and:

./configure
make
sudo make install

FFmpeg and all dependencies installed! Yay!

Take a breath.
Relax.
Sit back.
Rub your eyes.
Pet your cat or dog.
Have a beer.
Read a book.
You’re about halfway there.

On to the Dranger.com tutorial – How to Write a Video Player in Less than 1000 lines of Code

If you haven’t already, go ahead and check out the Dranger.com tutorial from which we will be drawing our video player code.

Just ignore the red box at the top of the page. The tutorial is slightly out of date because the ffmpeg function img_convert has been replaced with a new function sws_scale. Although this will become important a little later, we’re going to ignore it for the moment (this will provide us with some good opportunities for problem solving).

We need to make a directory for the tutorial code. Our “Downloaded Libraries” directory is as good a spot as any, so what I did was make a “videoplayer” subdirectory inside the “Downloaded Libraries” directory.

The first file we want to download and compile is “tutorial01.c”, and is available at the top of page 1 of the Dranger tutorial. Get the file and copy it to your “videoplayer” subdirectory.

In the terminal, cd to the “videoplayer” subdirectory and enter the following command, which is what is suggested on the Dranger.com website:

gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lm

What does all this mean:

  • gcc: is the compiler, the program (along with the help of “ld”, the linker) that turns the source files into an executable
  • -o tutorial01: specifies the name of the output file
  • tutorial01.c: is the source file we’re compiling
  • -lavutil -lavformat -lavcodec -lz -lm: are the external libraries that we need to include; the leading “l” stands for library; -lavutil is library avutil, one of the ffmpeg libraries; -lavformat is another ffmpeg library that deals with muxing and demuxing container formats; -lavcodec is another ffmpeg library that deals with encoding and decoding audio-visual streams; -lz is a compression library; and -lm is a math routine library.

Compiler errors

When I tried to compile the program, the terminal kicked out a mess of errors. A couple of points about compiler errors:

  1. don’t freak out if you get several hundred errors; often a single mistake can cause hundreds of errors;
  2. because of this, always start at the top and work down.

The first few lines of my terminal output looked like this (there were many more lines of errors):

tutorial01.c:22:28: error: ffmpeg/avcodec.h: No such file or directory
tutorial01.c:23:29: error: ffmpeg/avformat.h: No such file or directory
tutorial01.c:27: error: syntax error before '*' token
tutorial01.c: In function 'SaveFrame':
tutorial01.c:33: error: 'iFrame' undeclared (first use in this function)
tutorial01.c:33: error: (Each undeclared identifier is reported only once

What the first line is telling us is that in the file “tutorial01.c” on line 22 there was an error: it was unable to find the file “ffmpeg/avcodec.h”. The second number is the column number within the line.

Notice that the second error is very similar, except that it cannot find “avformat.h”. If we looked at lines 22 and 23 of the source file, we would see:

#include <ffmpeg/avcodec.h>
#include <ffmpeg/avformat.h>

These two files are the main header files for the ffmpeg library, and our compiler can’t find them.

A note about #include

A note about #include: You will will see two different forms of the #include directive:

#include <FILENAME>
#include "FILENAME"

The short but oversimplified explanation of the difference is that the quoted form searches for the file in the same directory as the source file. The angle-bracketed form searches through standard system locations for the included file. Thus quotes are typically used to include files local to the project and angle-brackets are used for pre-installed libraries, such as the dependencies we compiled and installed, as well as standard C libraries.

The more complete answer:

The angle-bracketed form first searches any directories specified by the -I compiler option, and then in the standard system include file locations as specified by the INCLUDE environment variable.

The quoted form searches the same directory as the file that contains the #include directive as well as the directories of any files that include that file. If the specified file is not found, then the compiler searches in the same locations as the angle-bracketed form.

The upshot of this is that if the included file is found in the same directory as the source file itself, our “videoplayer” directory in this instance, the quoted form needs to be used.

A relative path can also be used to specify locations in other directories.

#include "../../mylibrary/mylibrary.h" would tell the compiler to look two levels up in the directory hierarchy for a subdirectory called “mylibrary” and then for a file named “mylibrary.h”. If it didn’t find it, then it would search the other locations.

#include tells the compiler to search the directories specified by the -I option and then the standard include directories for a subdirectory named “ffmpeg” and within that subdirectory for a file named “avcodec.h”

If a path is specified as in the examples above, the compiler looks for the subdirectory before looking for the file, so the compiler will not find the file even if it is located in one of the search locations UNLESS it is located within the specified subdirectory or relative path.

It’s temping to just copy the header files into the project directory or simply try and use a different path to point the #include to the files–because we know where they are, right? They’re just sitting there in the “libavcodec” and “libavformat” subdirectories of the ffmpeg library source directory.

This would be a mistake. When we ran sudo make install the script copied the header files and the library files to one of our system directories. The problem is that our compiler is not finding the files. This could be caused by one of two things: either the install script put them in the wrong place or our PATH doesn’t include the proper directories.

Let’s start by finding the header files!

I could just go ahead and tell you that more than likely the header files have been installed in “usr/local/include”, but there’s a chance the install script put them somewhere else–and we’d miss the chance to talk about find and locate.

What are libraries?

About libraries: Libraries are reusable bits of code that other programs can “link” into so that they don’t have to continually reinvent the wheel, or the demuxer, in this case. In order for a program to link to a library, it must be able to locate the library file itself, which contains the actual code, and the header file, which is a text file, in our case written in C, that describes the functions and variables accessible in the library.

Say you want to use a function named “shazam” from “libmagic”. Without the header file, “libmagic.h”, when you reference “shazam” in your program the compiler will have no way of knowing if “shazam(42)” is a vaild function, nor will it know if you are passing the function the right parameters.

Library files can be statically linked and dynamically linked. Static linking means that the code used in the library is built into a program when it is compiled and linked. Thus once the program is built, the library is no longer needed. The program can be distributed as a stand-alone entity. Dynamically linking means that the program links to the library at run time, i.e. the code is not built into the program. Thus anyone who wants to run that program must have the required library on their system or the program will not run.

Static linking has the benefit of being self-contained and stable. Dynamic linking has the benefit of creating smaller executables and allowing for libraries to be updated.

On OSX, library files start with “lib”. Static libraries end with “.a” and dynamic libraries end with either “.dylib” or “.so”. Mac OSX also extensively uses frameworks that bundle the library files, the header files, and the metadata into one package. Most of the higher level system architecture is bundled this way. Frameworks end with “.framework”.

Find and Locate

find and locate are the two main commands used to find files in the terminal. locate works much faster because it searches a pre-built database. Unfortunately, it will not find files that have been added since the last database rebuild, which is done during the weekly maintenance scripts. find, because it searches the filesystem file-by-file, is much slower. However, it is guaranteed to find the file–as long as it exists.

So we have two choices:

  • 1) we can rebuild the locate database or
  • 2) we can use find.

If you’re only searching a few subdirectories, find would be faster. However, if you’re going to search the entire filesystem, it might be just as fast to go ahead and rebuild the locate database.

Find:

The command to search the entire filesystem for “avcodec.h” is:

find / -iname avcodec.h

Remember that the / specifies the root directory, so this tells find to begin searching in the root directory, and it will automatically search through all subdirectories. -iname avcodec.h tells find to look for a file named “avcodec.h”. The i in -iname specifies a case-insensitive search. Use -name to search case-sensitive.

If we wanted to search for both files, the command gets a little more complicated:

find / ( -iname avcodec.h -or -iname avformat.h )

The parenthesis group the search expression. Inside the parenthesis we have two search expressions grouped with the -or operator, so that find will find either of the files. Except that this won’t work because we have to escape the opening and closing parenthesis (which I left off for readability reasons). The escaped command is:

find / \( -iname avcodec.h -or -iname avformat.h \)

Suppose we knew that the files were located somewhere within the “/usr” directory. We could cd to the “/usr” directory and enter the following command (remember that period specifies the current directory):

find . -iname avcodec.h

Alternatively, from anywhere in the filesystem we could just use the absolute path of the “/usr” directory and enter:

find /usr -iname avcodec.h

One last point: if you want to suppress all of the “Permission Denied” errors, append 2>/dev/null to the end of the command, as in:

find / -iname avcodec.h 2>/dev/null

Locate:

locate is in some ways easier to work with. To find “avcodec.h” you simply enter:

locate avcodec.h

To make the search case insensitive, use the -i flag.

locate -i avcodec.h

However, because we’re looking for recent files, we have to first rebuild the database before locate will find them. There are two ways to do this:

  • sudo periodic weekly: this runs the weekly maintenance scripts, which include updating the locate database. This might take as long as ten minutes. But if you’re on a multiuser computer, this is the safer way to go.
  • sudo /usr/libexec/locate.updatedb: this updates the database directly, but does it from root. It will warn you that this will expose some files that would have otherwise been hidden from other users. On a single-user computer, this is no problem. It will also take around 1-3 minutes, much shorter than running the weekly maint. scripts.

To search for multiple files, you can simply separate them with a space:

locate avcodec.h avformat.h

Fix the PATH

After all that searching, I discovered that “avcodec.h” was located in the directory “/usr/local/include/libavcodec” and “avformat.h” was in the directory “/usr/local/include/libavformat”.

This is exactly where they are supposed to be, so what I needed to do was add the “/usr/local/include” directory to my PATH shell variable.

Remember that you can use the command $PATH to view the contents of your PATH. It’s entirely possible that this directory is already in your PATH (in which case you shouldn’t have gotten the same error as I did). Also, while you’re doing this, you ought to make sure that “/usr/local/lib” and “usr/local/bin” are in the PATH as well.

“/usr/local/lib” is where the library files themselves are installed (as opposed to the header files), and “/usr/local/bin” is where executable commands are installed.

To add these directories to your PATH, enter the following code, which will open an editor window:

open ~/.profile

Remember that the “~” is short-hand for the user base directory. “.profile” is your user profile file that is loaded when you open a terminal window.

Because I only needed to add “/usr/local/include” to my path, I added the following to the “.profile” file and saved it:

export PATH=$PATH:/usr/local/include

This code appends a new directory on the PATH variable. If you need to add all the directories to your PATH, use this code:

export PATH=$PATH:/usr/local/include:/usr/local/lib:/usr/local/bin

Another way to do this without opening an editor is to use the following command:

echo export PATH=$PATH:/usr/local/include >> ~/.profile

But be careful that you use “>>” because one “>” will overwrite your profile instead of appending, which you don’t want (like I just did, oops).

some hidden text

****You will need to restart your shell (terminal) for the new PATH variable to take effect.****

some hidden text

Specifying additional search locations in the command
You can tell gcc, the compiler, additional directories to search for libraries and header files with the -L and -I flags, respectively, as in:

gcc -o tutorial01 tutorial01.c -I/usr/local/include -L/usr/local/lib -lavutil -lavformat -lavcodec -lz -lm

However, in this case, this isn’t what we should do because these directories are standard installation directories that really should be included in our PATH.

Compile tutorial01.c again

Before we compile again, we need to fix one more thing. Remember that the #include statements in our source file read as:

#include <ffmpeg/avcodec.h>
#include <ffmpeg/avformat.h>

We need to change these to:

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>

Otherwise the compiler will be looking for an “ffmpeg” subdirectory in the library search path that doesn’t exist. I’m guessing that previous version of ffmpeg installed the header files together in a directory called “ffmpeg” but now they are separated out by the different libraries.

OK, one more time, compile the first tutorial file:

gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lm

More errors. My output was:

tutorial01.c: In function 'main':
tutorial01.c:175: warning: 'avcodec_decode_video' is deprecated (declared at /usr/local/include/libavcodec/avcodec.h:3480)
Undefined symbols:
     "_img_convert", referenced from:
          _main in ccUWquPk.o
ld: symbol(s) not found
collect2: ld returned 1 exit status

Sigh. Slump. We have two separate issues, one of them a show-stopper and the other not.

The second line is a warning, so it’s OK. What it’s telling me is that on line 175 of file “tutorial01.c” the function named “avcodec_decode_video” has been deprecated. We could ignore this if we were feeling lazy and didn’t want to learn anything.

What does deprecated mean?
Deprecated means that the function is being phased out. It will continue to work. However, it may be removed in subsequent versions. It’s a warning to developers to start fixing their code before it breaks. If we were developing a program for distribution, we would need to update this function to the newer version.

The next problem is an error, which we have to fix. Undefined symbols: "_img_convert", referenced from: _main" tells us that the code attempted access a symbol, img_convert from the function main.

Notice the underscore before the names: we can drop these, they’re added for esoteric reasons we don’t really need to go into; just realize that when the linker references a function or variable name, it’s probably gonna add an extraneous underscore before it.

Also notice ld: symbol(s) not found. The ld means that this is a linking error and not a compiling error. ld is the linker, the program that assembles the object files (or Leggo pieces) into a final program (or starship). This means that the error wasn’t a problem with the code per se, but a problem finding a function or variable referenced from another library or object file. In this case the function img_convert, which was supposed to be defined in one of the ffmpeg libraries.

Typically if you have an undefined symbol in code it means one of two things:

  1. The symbol itself is spelled wrong
  2. The external file in which the symbol is defined was not found by the linker

In this case, the answer is neither 1 nor 2 but 3: the function img_convert has been deprecated for so long that it’s finally been dropped from the library, as was explained in the red warning box on the Dranger.com tutorial and has been replaced with a new function sws_scale.

There are quite a few different ways we could deal with this problem:

  1. Quit
  2. Cry
  3. Go outside and hug a tree or something and hope that the problem’s disappeared when we return and reboot our computer
  4. Ask our computer really, really nicely if it would compile the program anyway
  5. Download, compile, and install an older version of ffmpeg (0.5.2 or so) from before the img_convert function was removed
  6. Update the img_convert function to swscale

(1) would only delay the problem–sooner or later we’d just have to come back and try it again. (2) is just what it wants, and we can’t have that. (3) – well, sadly this is one of the few computer problems that a reboot will not fix. (4) you can try if you want, but I’m guessing the terminal doesn’t give a damn if you talk nicely to it. In fact, only (5) and (6) are likely to meet with success.

We’ll do (6) because (5) is a bit of a cheat. Fortunately, as we walk in the footsteps of other aspiring ffmpeg’ers, the hard work has already been done for us.

Let’s have a moment of appreciative silence for the internet.
Because…
Good god is this stuff easier these days. I remember trying to figure out how to program C when I was a young boy. Pre-internet, growing up in one-stoplight town in Texas, learning to program was a bit like learning to assemble an Ikea bed blindfolded, one-handed, and with the nearest Ikea over an hour away so every time I needed a new metal plug or screw I had to drive two hours roundtrip (this was, of course, pre-Ikea too, but whatever, really, I overstretched the metaphor as well, so humph).

Updating img_convert to sws_scale – An intro to GREP!

The first step in updating img_convert is to find the reference to the function in the “tutorial01.c” file. Of course, we could simply open the file and search for it, or simply scroll through it as it’s not that big of a file, but that wouldn’t be any fun, and we wouldn’t get to learn about grep.

grep is used when you want to search the contents of files, as opposed to find and locate which just search the file names.

In your terminal, navigate to the “videoplayer” directory and enter the following command:

grep -i -n img_convert *

The flags mean:

  • -i : case insensitive search, not really necessary but I like to do this just in case (haha)
  • -n : include line numbers
  • img_convert : the string we are searching for; if there are spaces, use quotes
  • * : search all files in the current directory

Which returns the following:

tutorial01.c:133:       img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,
tutorial02.c:137:       img_convert(&amp;pict, PIX_FMT_YUV420P,
tutorial03.c:331:       img_convert(&amp;pict, PIX_FMT_YUV420P,
tutorial04.c:400:    img_convert(&amp;pict, dst_pix_fmt,
tutorial05.c:464:    img_convert(&amp;pict, dst_pix_fmt,
tutorial06.c:573:    img_convert(&amp;pict, dst_pix_fmt,
tutorial07.c:581:    img_convert(&amp;pict, dst_pix_fmt,
tutorial08.c:514:  static struct SwsContext *img_convert_ctx;
tutorial08.c:573:    if(img_convert_ctx == NULL) {
tutorial08.c:576:      img_convert_ctx = sws_getContext(w, h,
tutorial08.c:579:      if(img_convert_ctx == NULL) {
tutorial08.c:584:    sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize,

Notice that the search also matched img_convert_ctx even though this is a different symbol. The results shows us that tutorials 1 through 7 contain the defunct img_convert function, so if we were going to try and update all of the tutorial files (which we’re not), we would now know exactly where we needed to focus our efforts.

If you wanted to just search “tutorial01.c”, we could have used this command:

grep -i -n img_convert tutorial01.c

Which would return:

133: img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,

It’s also possible to specify multiple files, such as:

grep -i -n img_convert tutorial01.c tutorial02.c

So now that we know where we need to look in “tutorial01.c” for the offending function, lets go ahead and fix the code. Thankfully, David Hoerl has done the heavy hitting for us and posted updated code on his blog. However, you don’t have to download the sample file from his webpage because I’m gonna show you what we need to do right here.

Find lines 132-135 in “tutorial01.c”:

// Convert the image from its native format to RGB
img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,
           (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width,
           pCodecCtx->height);

And replace them with the following:

static struct SwsContext *img_convert_ctx;

// Convert the image into YUV format that SDL uses
if(img_convert_ctx == NULL) {

     int w = pCodecCtx->width;
     int h = pCodecCtx->height;

     img_convert_ctx = sws_getContext(w, h,
				      pCodecCtx->pix_fmt, w, h,
				      PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);

     if(img_convert_ctx == NULL) {
	  fprintf(stderr, "Cannot initialize the conversion context!\n");
	  exit(1);
     }

}

int ret = sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize,
	  0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);

Compile “tutorial01.c” again

Now that we’ve got the tutorial code updated, let’s try and compile the program again. Here’s the gcc command we used. Undoubtedly, now, after all that, everything will work just fine.

gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lm

The up arrow: press it!
One really nifty feature of the terminal is the up arrow. Press it! (in the terminal, of course). It will scroll through you recent commands. This is especially handy when you’re doing a lot of compiling because you don’t have to constantly retype or even remember all those options you specified. I love it. Whoever invented it should get the peace prize–just think of all the fights its prevented between grumpy unix geeks hyped up on coffee and pipe tobacco at five in the morning typing and re-typing “-lz -lm -lavcodec” over and over again.

Ooops. Errors:

tutorial01.c: In function 'main':
tutorial01.c:127: warning: 'avcodec_decode_video' is deprecated (declared at /usr/local/include/libavcodec/avcodec.h:3480)
tutorial01.c:143: error: 'SWS_BICUBIC' undeclared (first use in this function)
tutorial01.c:143: error: (Each undeclared identifier is reported only once
tutorial01.c:143: error: for each function it appears in.)
tutorial01.c:143: warning: assignment makes pointer from integer without a cast

We can still ignore the warning about “avcodec_decode_video is deprecated”. However, we do have an undeclared symbol: SWS_BICUBIC. If you look at the line number and check the code, you’ll realized that this is one of the lines that we just added.

Now, we could reason through this. There are several hints as to what’s going on. (SWS?) However, instead of thinking, lets grep our way out of this. We know that SWS_BICUBIC is most likely supposed to be defined somewhere in the ffmpeg library.

cd to the root directory of the ffmpeg source code. We’re gonna use grep to search through the library source for the definition of the missing symbol.

Our new command is (and we’re gonna introduce a couple new flags, and don’t freak out about the letters being crammed together, it means the same thing, it’s just shorthand):

grep -iInr --exclude="*\.svn*" SWS_BICUBIC *

The options mean:

  • i : case insensitive search, not really necessary but I like to do this just in case (haha)
  • I : excludes binary files, such as compiled object files and executable files (we only care about source files, which are text)
  • n : include line numbers
  • r : recursive search, meaning that it will search subdirectories as well
  • --exclude="*\.svn*" : this tells grep to ignore hidden directories created by SVN (because it’ll just be confusing and we don’t care about them)
  • SWS_BICUBIC : the string we are searching for; if there are spaces, use quotes
  • * : search all files and subdirectories in the current directory

The mighty grep rewarded me with the following response:

ffmpeg.c:230:static unsigned int sws_flags = SWS_BICUBIC;
ffplay.c:83:static int sws_flags = SWS_BICUBIC;
libavformat/output-example.c:41:static int sws_flags = SWS_BICUBIC;
libswscale/options.c:39:    { "bicubic", "bicubic", 0, FF_OPT_TYPE_CONST, SWS_BICUBIC, INT_MIN, INT_MAX, VE, "sws_flags" },
libswscale/swscale-test.c:192:                          SWS_BILINEAR, SWS_BICUBIC,
libswscale/swscale.h:64:#define SWS_BICUBIC           4
libswscale/utils.c:240:        if      (flags&amp;SWS_BICUBIC)      sizeFactor=  4;
libswscale/utils.c:274:                if (flags &amp; SWS_BICUBIC) {
libswscale/utils.c:761:                |SWS_BICUBIC
libswscale/utils.c:933:                           (flags&amp;SWS_BICUBLIN) ? (flags|SWS_BICUBIC)  : flags,
libswscale/utils.c:953:                       (flags&amp;SWS_BICUBLIN) ? (flags|SWS_BICUBIC)  : flags,
libswscale/utils.c:1032:        else if (flags&amp;SWS_BICUBIC)

The sixth line holds the answer. SWS_BICUBIC is defined in the header file “swscale.h”, which we have not included. So we need to add an include directive to the top of our “tutorial01.c” file (anywhere among the other #include directives).

#include <libswscale/swscale.h>

So now, surely, we’re ready. Use your up arrow and compile again.

Damn.

tutorial01.c: In function 'main':
tutorial01.c:128: warning: 'avcodec_decode_video' is deprecated (declared at /usr/local/include/libavcodec/avcodec.h:3480)
tutorial01.c:149: warning: passing argument 2 of 'sws_scale' from incompatible pointer type
Undefined symbols:
  "_sws_scale", referenced from:
      _main in ccoXcSMI.o
  "_sws_getContext", referenced from:
      _main in ccoXcSMI.o
ld: symbol(s) not found
collect2: ld returned 1 exit status

OK, ignoring the two warnings, we have two undefined symbols. Now remember, these are linker errors (ld: symbol(s) not found), which means that the error was not in the building of the object files (the Leggos) but in the piecing of the files together.

What are these symbols and why can’t the linker find them?

Drop the leading underscore, and notice that both symbols start with “sws”. It’s an easy bet that this means they are defined in “libswscale”. If it weren’t this easy to guess where these symbols were defined, this would be a great place to use grep to search the library source files to discover in which library or file the symbols were defined.

So the linker is not finding the library for some reason. We know that the PATH is correct because the linker is finding our other ffmpeg libraries. Let’s verify that the library “libswscale” is actually where it’s supposed to be.

ls /usr/local/lib

I won’t list the entire contents of my “/usr/local/lib” here because there are quite a few files in there. However, I did see the following among them:

libswscale.a
libswscale.dylib

Incidentally, I could have used ls /usr/local/lib/libswscale* to search the library directory for files starting with “libswscale”.

Thus I know that the library files are installed. The problem must lie elsewhere. After some head scratching, and reminding me never to discount operator error, I notice that I forgot to include the swscale library in the compiler command. Ooops, again. The compiler only links to libraries that it is specifically told to link to in the command line options, thus we need to add -lswscale to the compiler command.

gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lm -lswscale

Holy…uh, yeah. Success!

tutorial01.c: In function 'main':
tutorial01.c:128: warning: 'avcodec_decode_video' is deprecated (declared at /usr/local/include/libavcodec/avcodec.h:3480)
tutorial01.c:149: warning: passing argument 2 of 'sws_scale' from incompatible pointer type

So this is what success looks like in the wonderful world of programming. Failure is long, success is short. If we had tendencies to obsessive-compulsive disorder, we could hammer away at those two warnings. They wouldn’t actually be that hard to get rid of, the first one requires replacing avcodec_decode_video with a newer function avcodec_decode_video2 and changing a couple params. Likely the second simply requires casting argument two to the proper type. However, we’ve done enough.

Let’s test our program.

You’ll need some type of movie file to test. My file is named “test.mov”. The file extension tells me the file is a Quicktime file, and unless the codecs used to compress the contained streams are truly esoteric (remember Quicktime is a container format and can contain dozens of different types of encoded streams), I’m sure ffmpeg can handle them.

Our test program, “tutorial01.c”, if everything goes well, should spit out the first five frames of the video file. The command for me to run the program is (don’t forget that we need the ./):

./tutorial01 test.mov

My successful output looked like:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test.mov':
  Metadata:
    major_brand     : qt
    minor_version   : 537199360
    compatible_brands: qt
  Duration: 00:04:19.21, start: 0.000000, bitrate: 381 kb/s
    Stream #0.0(eng): Video: h264, yuv420p, 640x480, 186 kb/s, 11.96 fps, 600 tbr, 1200 tbn, 2400 tbc
    Stream #0.1(eng): Audio: mp3, 44100 Hz, 2 channels, s16, 192 kb/s
[swscaler @ 0x3bf000] No accelerated colorspace conversion found from yuv420p to rgb24.

And sure enough, in my “videoplayer” directory I found five “ppm” files. On my computer, when I type open frame1.ppm, the files open in NeoOffice. Preview doesn’t seem to open them. The format is a Portable Pixel Map. Looking at the SaveFrame function code, I’m guessing that this is just a very simple file format that was easy to write.

A confession

So I have to admit that if you don’t care about learning, the last hour or so of your life was a complete waste of time. But that’s only if you don’t care about learning. I say this because the last file we’re gonna compile, “tutorial08.c”, the final file in the Dranger.com – How to Write a Video Player in Less than 1000 lines of Code – has already been updated to use sws_scale, so we could have just skipped straight to that.

Compiling tutorial08.c

Download “tutorial08.c” from the Dranger tutorial and place it in your “videoplayer” subdirectory.

There are a couple changes we need to make before we compile.

Find the two #include directives for the SDL library: SDL.h and SDL_thread.h, and append “SDL/” in front of them so that they look like this:

#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>

If you forget to do this, the compiler won’t find the SDL header files and you’ll get a TON of errors. The next thing we need to do is include a new option in the compiler command:

`sdl-config --cflags --libs`

Now this option is a little different. There’s a program that the SDL library installed called “sdl-config”, and it’s job is to output the gcc compiler options for you.

You can run sdl-config --cflags --libs from the terminal and see what the program does. On my computer it told me:

-I/usr/local/include/SDL -D_GNU_SOURCE=1 -D_THREAD_SAFE -L/usr/local/lib -lSDLmain -lSDL -Wl,-framework,Cocoa

When you include that command in the compiler options in quotes, it substitutes sdl-config’s output in place of what’s in the quotes, automatically configuring your compiler appropriately for you.

So it turns this:

gcc -o tutorial08 tutorial08.c -lavutil -lavformat -lavcodec -lz -lm -lswscale `sdl-config --cflags --libs`

Into this:

gcc -o tutorial08 tutorial08.c -lavutil -lavformat -lavcodec -lz -lm -lswscale -I/usr/local/include/SDL -D_GNU_SOURCE=1 -D_THREAD_SAFE -L/usr/local/lib -lSDLmain -lSDL -Wl,-framework,Cocoa

Pretty neat, huh?

A workaround – skip if sdl-config is working for you

Initially when I tried to run the compiler command with the `sdl-config --cflags --libs` in it, for some reason my terminal just didn’t want to find the sdl-config program. It kept telling me sdl-config: command not found. If you’re not having problems with sdl-config you can skip this sub-section.

After much bashing of my head against the shiny screen, I decided to implement a good, old-fashioned workaround.

You can type which sdl-config to find out if and where the program is installed. It should have been installed in “/usr/local/bin”. If it’s not there, then something’s wrong with your SDL installation. However, if this were the case, your program probably would not have compiled either, because most likely the libraries would not have been installed.

I was able to run sdl-config from the command line, so what I did was execute the command sdl-config --cflags --libs and copy the result into a temporary shell variable like this:

TEMP_VAR="-I/usr/local/include/SDL -D_GNU_SOURCE=1 -D_THREAD_SAFE -L/usr/local/lib -lSDLmain -lSDL -Wl,-framework,Cocoa"

I then checked to make sure the variable had been stored properly by typing:

echo $TEMP_VAR

And ran the compiler with the following command, using TEMP_VAR:

gcc -o tutorial08 tutorial08.c -lavutil -lavformat -lavcodec -lz -lm -lswscale $TEMP_VAR

This was, of course, a bit of a cheat, and what I should have done was try and figure out why it wasn’t working, but sometimes you just want to get on with things, and fortunately, it magically started working again. Another idea would have been to run the sudo make install again.

So when I build the program, I get the following:

tutorial08.c: In function 'audio_decode_frame':
tutorial08.c:289: warning: 'avcodec_decode_audio2' is deprecated (declared at /usr/local/include/libavcodec/avcodec.h:3418)
tutorial08.c: In function 'queue_picture':
tutorial08.c:581: warning: passing argument 2 of 'sws_scale' from incompatible pointer type
tutorial08.c: In function 'video_thread':
tutorial08.c:657: warning: 'avcodec_decode_video' is deprecated (declared at /usr/local/include/libavcodec/avcodec.h:3480)
tutorial08.c: In function 'decode_thread':
tutorial08.c:862: warning: passing argument 1 of 'url_ferror' from incompatible pointer type
tutorial08.c: In function 'SDL_main':
tutorial08.c:947: warning: pointer targets in assignment differ in signedness
Undefined symbols:
  "_pstrcpy", referenced from:
      _SDL_main in ccdj20Sw.o
ld: symbol(s) not found
collect2: ld returned 1 exit status

Argh. Ugh. Another undefined symbol. Getting tired of these yet?

Let’s learn a new flag. Add -w to our build command to suppress the warnings, as in:

gcc -o tutorial08 tutorial08.c -lavutil -lavformat -lavcodec -lz -lm -lswscale `sdl-config --cflags --libs` -w

Which cuts the output down to the nitty gritty:

Undefined symbols:
  "_pstrcpy", referenced from:
      _SDL_main in ccdj20Sw.o
ld: symbol(s) not found
collect2: ld returned 1 exit status

pstrcpy, my dear friend

Now, I’m guessing that pstrcpy is a string copy function because I know that strcpy is a standard C string copy function. Unfortunately, the linker deals with code that’s already compiled, i.e. translated into machine code, so it can’t tell us where the symbol is located. In fact, what it does tell us is rather confusing.

It’s saying that the symbol pstrcpy was referenced from a function SDL_main in the object file “ccdj20Sw.o”.

The name of the object file is random. Because we’re compiling and linking in one step, our compiler is creating a randomly named intermediate file. And with only one source file, “tutorial08.c”, it’s not hard to figure out where the problem is, assuming that there’s not an error in one of the libraries.

However, there is one confusing problem. There’s no SDL_main function in “tutorial08.c”. What’s going on here is that our source file is really being run from within the SDL framework, in fact, from a function named SDL_main. Thus when the program is linked, our code is actually being executed from a function so named.

So let’s use grep to search our source file for the offending function:

grep -iIn pstrcpy tutorial08.c

And it tells us:

932:  pstrcpy(is->filename, sizeof(is->filename), argv[1]);

Yep. Line 932. A call to the offending function.

So what’s up with pstrcpy?

Let’s turn to our old friend google. A simple search for “ffmpeg pstrcpy” brings up a hit titled “av_strlcpy instead of pstrcpy,” which pretty much says it all. pstrcpy has been replaced with an updated function named av_strlcpy.

Don’t get smacked by satan – avoid the buss errors!

Now, if you get too excited and just dive right into the file and change the name without checking the function definitions you’ll get smacked by satan with a buss error. And we don’t want that. So let’s figure out where av_strlcpy is defined.

Although we certainly could use our trusty friend grep to search the ffmpeg library, as in grep -iIrn av_strlcpy * from the ffmpeg source directory, this is going to spit out not only the one, lonely place where av_strlcpy is defined but also every place it’s used. And it’s used a lot.

So lets use our heads. Why not? It’s OK every once in a while.

If we examine the ffmpeg directory we’ll notice a library named “libavutil”. We’ll, a string copy function in an audio-visual library sure seems like it would be a “utility” function. And if we look inside the “ffmpeg/libavutil” directory we see a source file named “avstring.c” and it’s header file “avstring.h”.

Function definitions are typically explained in the header files, so lets open “avstring.h” and see what we find. Our function is defined on lines 63-78.

/**
 * Copy the string src to dst, but no more than size - 1 bytes, and
 * null-terminate dst.
 *
 * This function is the same as BSD strlcpy().
 *
 * @param dst destination buffer
 * @param src source string
 * @param size size of destination buffer
 * @return the length of src
 *
 * WARNING: since the return value is the length of src, src absolutely
 * _must_ be a properly 0-terminated string, otherwise this will read beyond
 * the end of the buffer and possibly crash.
 */
size_t av_strlcpy(char *dst, const char *src, size_t size);

Our original function call was this:

pstrcpy(is->filename, sizeof(is->filename), argv[1]);

You could be forgiven for not knowing that argv[1] is the first parameter passed to the program at the command line, and thus in this case the name of the file to be processed.

av_strlcpy expects the parameters: destination, source, size.
pstrcpy expects the parameters: destination, size, source.

Thus we need to change the name of the function and swap the last two parameters. That is, unless you enjoy getting smacked by Satan with a buss error, in which case, feel free.

This is your new function call (paste it over the old one in “tutorial08.c” on line 932):

av_strlcpy(is->filename, argv[1], sizeof(is->filename));

And then compile (suppressing warnings):

gcc -o tutorial08 tutorial08.c -lavutil -lavformat -lavcodec -lz -lm -lswscale `sdl-config --cflags --libs` -w

What?

Could it be?

Did we?

Drumroll, please…Let’s run the video player!

With the warnings suppressed, gcc output a big, fat, cheery nothing to my terminal.

(A big, fat, cheery nothing!)

It also output a “tutorial08” executable file. So let’s test our video player (with gracious thanks to Stephen Dranger for his great, if slightly out of date, tutorial). I hesitate to even bother telling you the command at this point, but regardless, here it is:

./tutorial08 test.mov &

Remember & spawns a new process (in a separate thread) for the video player so that we don’t tie up our precious terminal window while the movie is playing.

SUCCESS!! SUCCESS!!

I’ll say it again – SUCCESS!

Want a hug? I hope it worked for you. If it didn’t, I hope you’ve learned enough skills to attempt to troubleshoot the problem, because that was really the point, wasn’t it. And just in case, I’ve included a screen shot of my computer with the working tutorial08 program playing what appears to be a MaxMSP tutorial video so that you can bask in my success.


A fond farewell, and a parting bit of wisdom…

As a reward for having made it this far into the tutorial, I’ll share with you a joke. But not just any joke, a really smart joke. Not only is this joke multidisciplinary, spanning computer science, philosophy, and fine art, it’s also recursive and self-referentially post-modern. I’m pretty excited about it.

Just click the link below.

This is not a joke!

58 Responses to “Building ffmpeg: a rather long, sometimes humorous, and cheerfully detailed problem solving guide to compiling open-source software on Mac OSX”

  1. injectable-steroids.net Says:

    Submitted to FAO by Boehringer Ingelheim Vetmedica GmbH, Ingelheim, Germany.

  2. Teragon Labs Testosterone Enanthate Says:

    We are in business to change lives not simply run a website.

  3. steroids store canada Says:

    Refreshing, single ingredient meats will not have added nitrates.

  4. clenbuterol side effects webmd Says:

    Ashton says he unknowingly ingested the prohibited substance when this individual borrowed an inhaler
    used to deal with asthma from another athlete.

  5. Teragon Labs Primobolan Says:

    More than 17, 1000 people saw him end second in the 40 metres in 5.
    77 seconds.

  6. buy steroids canada paypal Says:

    Their common use as analgesic adjuvants for bony, visceral,
    and neuropathic pain is broadly maintained
    expert opinion.

  7. yazidapple Says:

    hi guys.

    when i tried to compile the tutorial01.c using terminal, i came across this error.

    Yazidapples-MacBook-Pro:ffyazid Yazidapple$ gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lm
    Undefined symbols for architecture x86_64:
    “_sws_getContext”, referenced from:
    _main in tutorial01-ed3495.o
    “_sws_scale”, referenced from:
    _main in tutorial01-ed3495.o
    ld: symbol(s) not found for architecture x86_64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)

    what is wrong with my setup?
    anyone can help?

  8. เบอร์มงคล ขอนแก่น Says:

    Hello there I am so thrilled I found your blog page, I really found you by
    mistake, while I was researching on Askjeeve for something else, Anyways I
    am here now and would just like to say thanks for a remarkable
    post and a all round enjoyable blog (I also love the theme/design), I don’t have time to browse it
    all at the minute but I have bookmarked it and also
    added your RSS feeds, so when I have time I will be back to read much more, Please do keep up the excellent b.

Leave a Reply

Why ask?