Pages

Saturday, September 25, 2010

FFMpeg - Using Libavcodec in your program


This will be my second post on ffmpeg. The first post was on building ffmpeg on windows using mingw.
In this post, we will see how you can use those libraries in your application and decode movie files. I am taking dranger's first tutorial as the source and I will build it using Visual C++ 6.0. You can find the source code here.

This post has been published in CodeProject.com. You can view it here. The Codeproject image appearing in this post will indicate that the articles are also present in CodeProject.

Explanation of the code

Opening the video file

// Register all formats and codecs
av_register_all();

// Open video file
if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0)
return -1; // Couldn't open file

// Retrieve stream information
if(av_find_stream_info(pFormatCtx)<0)
return -1; // Couldn't find stream information

// Dump information about file onto standard error
dump_format(pFormatCtx, 0, argv[1], 0);

First, we initialize ffmpeg by calling 'av_register_all()' at the starting of the program. This registers all supported formats and codecs.
Next we open the video file using av_open_input_file(). The first parameter is the pointer to the 'AVFormatContext' which we will use in our program to refer to the video file. The second parameter is the name of the file to be opened. The last three parameters are for file format, buffer size, and format options. By setting it to NULL and 0, libavformat detects and fills values on it own.
We dump information about the input file using 'dump_format()'

Opening the decoder

videoStream=-1;
for(i=0; inb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
  videoStream=i;
  break;
}
if(videoStream==-1)
return -1;
pCodecCtx=pFormatCtx->streams[videoStream]->codec;

We loop through the list of streams present in the input file to locate the 'video' stream present in it. The file can typically contain one or more audio/video streams in it. Once a video stream is located, we extract the codec type of the video stream. This codec type will be used to initialize the decode as shown below. avcoded_find_decoder accepts the codec id (obtained from the video file) as input and returns a pointer of type 'AVCodec'. The codec is then open by making a call to 'avcodec_open()'

pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
if(avcodec_open(pCodecCtx, pCodec)<0)
return -1;

Allocating buffers for decoding

numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,  pCodecCtx->width, pCodecCtx->height);

This part of the code is fairly elementary. We calculate the size of the buffer required, allocate a temporary buffer using malloc

Decoding frames

i=0;
while(av_read_frame(pFormatCtx, &packet)>=0) {
if(packet.stream_index==videoStream) {
  avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size); 
  if(frameFinished) {
  img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,
                (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width,
                pCodecCtx->height);

if(++i<=5)
  SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height,
        i);
  }
}

Well, this is the part of the code that excites me the most, decoding the frames :) You read frames the file using av_read_frame. This function returns a non-zero positive value if the file has not reached its end. After receiving a packet, we first check if it is a video packet. We pass the CodecContext, Packet data and size as the input to the function. If decoding of the packet is successful, the 'pFrame' pointer points to decoded data. Success or failure of the decoding process is indicated by the 'frameFinished'. If it fails, the value will be '0'. We then convert the data from YUV to RBG format so that we can write to the file. I guess, by default, the decoded data is of type YUV.

Cleanup

av_free(buffer);
av_free(pFrameRGB);
av_free(pFrame);
avcodec_close(pCodecCtx);
av_close_input_file(pFormatCtx);

This is the part of the code i normally hate. But any mistake in this part will result in memory leaks or run time exceptions. So we will release all the allocated buffers and frames using av_free(). We will also close the codecontext and file using avcodec_close() and av_close_input_file() respectively.

Steps to build the application
  1. Create a win32 console application using Visual C++. Let us name it 'ffmpeg'.
  2. Create a folder named 'Libraries' and copy the following library files (avcodec.lib, avformat.lib, avutil.lib) to that folder. 
  3. Add 'tutorial1.c' to the project.
  4. Open the 'Project Settings' dialog box and in the C++ tab, choose the 'Preprocessor' option. In the 'Additional Include Directories', add the folder where the ffmpeg header files are present. (ex: C:\ffmpeg) 
  5. Navigate to the 'Link' tab in the project settings dialog and choose 'Input' as category. Add the following libraries to the 'Object/Library modules' text box : avformat.lib avcodec.lib avutil.lib. Add 'libraries' to the 'Additional Library path' text box.
  6. Build the application and you should be able to build it without any problem.
I hope to write on more post on encoding data using FFmpeg. That will complete the cycle.

Sunday, September 12, 2010

Mr. Fern - Close up

This is my first post on photography. This photo was taken with a kodak digital camera. The photo came up better than I thought.



Friday, September 3, 2010

Windows service using C

For a very long time I did not know what a windows service was. When I came to what it was, I thought I had learnt something important far too late. As they say, 'It is better to be late then never at all' ;) For people who don't know what a service is, A service is a console application that runs in the background and performs tasks that don't require user interaction. The Windows NT/2000/XP operating systems offer special support for service programs. The installed services can be configured through the Services applet, available from the Control Panel in Windows NT or from Control Panel | Administrative Tools in Windows 2000/XP. Services can be configured to start automatically when operating system starts, so you dont have to start each of them manually after a system reboot

Writing a windows service and installing on windows was a lot easier than I thought. This blog will explain how to write a simple windows service using C and install it.

A window's service program contains 3 parts,
  1. Main function
  2. Windows Service's Main function
  3. Control Handler function

The main function does nothing but lists the services that this program contains.
The program contain any number of services, the list must be terminated with a NULL entry into the list.

The Window's Service main is the entry point for your service.  This function should accomplish 3 things. It must set the appropriate status of the service using SetServiceStatus. The argument to the function is a SERVICE_STATUS. This function should register the controlhandler function for this service. The functionality of the control handler will be explained below. And finally, this function should not exit. If it does, the service will move to a 'STOPPED' state in the services applet.An ideal way of implementing this is using a while loop which checks for the state of the service status variable. (shown below).

The control handler handles the request to the service such as stopping it. Stopping the service can be triggered from the services applet. All cleanup code that the service must execute while exiting must be written here.

Sample Program,  SampleWindowsService.cpp

#include
#define SLEEP_TIME 5000

SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;

void  ServiceMain(int argc, char** argv);
void  ControlHandler(DWORD request);

void main()
{
    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = "MemoryStatus";
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;
    // Start the control dispatcher thread for our service
    StartServiceCtrlDispatcher(ServiceTable); 
}

void ServiceMain(int argc, char** argv)
{
    int error;

    ServiceStatus.dwServiceType        = SERVICE_WIN32;
    ServiceStatus.dwCurrentState       = SERVICE_START_PENDING;
    ServiceStatus.dwControlsAccepted   =  SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    ServiceStatus.dwWin32ExitCode      = 0;
    ServiceStatus.dwServiceSpecificExitCode = 0;
    ServiceStatus.dwCheckPoint         = 0;
    ServiceStatus.dwWaitHint           = 0;

    hStatus = RegisterServiceCtrlHandler(
        "MemoryStatus",
        (LPHANDLER_FUNCTION)ControlHandler);


    if (hStatus == (SERVICE_STATUS_HANDLE)0)
    {
        // Registering Control Handler failed
        return;
    } 

    // We report the running status to SCM.
    ServiceStatus.dwCurrentState = SERVICE_RUNNING;
    SetServiceStatus (hStatus, &ServiceStatus);

    MEMORYSTATUS memory;
    // The worker loop of a service
    while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
    {
        Sleep(SLEEP_TIME);
    }
    return;
}

// Control handler function
void ControlHandler(DWORD request)
{
    switch(request)
    {
        case SERVICE_CONTROL_STOP:
            ServiceStatus.dwWin32ExitCode = 0;
            ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
            SetServiceStatus (hStatus, &ServiceStatus);
            return;

        case SERVICE_CONTROL_SHUTDOWN:
            ServiceStatus.dwWin32ExitCode = 0;
            ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
            SetServiceStatus (hStatus, &ServiceStatus);
            return;
       
        default:
            break;
    }

    // Report current status
    SetServiceStatus (hStatus,  &ServiceStatus);

    return;
}

To install this service on your operating system, you'll need to use the SC.EXE executable, which comes with the Win32 Platform SDK tools. You will use this utility to install and remove the service. The other control operations will be done through the Services applet.
Here is the command-line to install your windows service:

sc create SampleWindowsService binpath= 
c:\MyServices\SampleWindowsService.exe
 
To remove the service from the system, execute the following command:

sc delete SampleWindowsService

Specify the delete option and the service name. The service will be marked for deletion and will be completely removed after the next restart.