Stas,

An update (quite a long one I'm afraid). I changed my code so that all
of the JPEG writing stuff goes into its own function as below (once
again, the e-mail system has ruined all of my beautiful code
formatting):

//
//      Write JPEG
//
//      Converts and writes the video frame in pFrame to a JPEG file
//
//      Parameters:
//              pCodexCtx       Pointer to the video stream codec
context
//              pFrame  Pointer to Frame exracted from the video stream
//              FrameNo FrameNumber
//              Quality Image quality as %
//
//      Returns:
//              Size of buffer written or 0 on error
//
int
WriteJPEG ( AVCodecContext *pCodecCtx, AVFrame *pFrame, int FrameNo, int
Quality )
{
        AVCodecContext  *pOCodecCtx;
        AVCodec           *pOCodec;
        BYTE                    *Buffer;
        int                     BufSiz;
        int                     BufSizActual;
        int                     ImgFmt = PIX_FMT_YUVJ420P;
        FILE                    *JPEGFile;
        char                    JPEGFName[256];

        BufSiz = avpicture_get_size ( ImgFmt, 
                                    pCodecCtx->width, 
                                    pCodecCtx->height );
        //
        //      Allocate local RGB frame buffer
        //
        Buffer = (BYTE *)malloc ( BufSiz );
        if ( Buffer == NULL ) 
                return ( 0 );
        memset ( Buffer, 0, BufSiz );
        //
        //      Allocate the output codec context
        //
        pOCodecCtx = avcodec_alloc_context ( );
        if ( !pOCodecCtx ) {
                free ( Buffer );
                return ( 0 );
                }
        //
        //      Initialise format parameters
        //
        pOCodecCtx->bit_rate      = pCodecCtx->bit_rate;
        pOCodecCtx->width         = pCodecCtx->width;
        pOCodecCtx->height        = pCodecCtx->height;
        pOCodecCtx->pix_fmt       = ImgFmt;
        pOCodecCtx->codec_id      = CODEC_ID_MJPEG;
        pOCodecCtx->codec_type    = CODEC_TYPE_VIDEO;
        pOCodecCtx->time_base.num = pCodecCtx->time_base.num;
        pOCodecCtx->time_base.den = pCodecCtx->time_base.den;
        //
        //      Allocate the JPEG encoder
        //
        pOCodec = avcodec_find_encoder ( pOCodecCtx->codec_id );
        if ( !pOCodec ) {
                free ( Buffer );
                return ( 0 );
                }
        //
        //      Open the JPEG encoder
        //
        if ( avcodec_open ( pOCodecCtx, pOCodec ) < 0 ) {
                free ( Buffer );
                return ( 0 );
                }
        //
        //      Initialise the "VBR" settings
        //
        pOCodecCtx->qmin           = pOCodecCtx->qmax = Quality;
pOCodecCtx->mb_lmin        = pOCodecCtx->lmin = 
                                            pOCodecCtx->qmin *
FF_QP2LAMBDA;
        pOCodecCtx->mb_lmax        = pOCodecCtx->lmax = 
                                            pOCodecCtx->qmax *
FF_QP2LAMBDA;
        pOCodecCtx->flags          = CODEC_FLAG_QSCALE;
        pOCodecCtx->global_quality = pOCodecCtx->qmin * FF_QP2LAMBDA;
        //
        //      Set the timestamp and quality parameters
        //
        pFrame->pts     = 1;
        pFrame->quality = pOCodecCtx->global_quality;
        BufSizActual = avcodec_encode_video ( pOCodecCtx, 
                                            Buffer,
                                            BufSiz,
                                            pFrame );
        //
        //      Write JPEG to file
        //
        sprintf ( JPEGFName, "f%06dq%02d.jpg", FrameNo, Quality );
        JPEGFile = fopen ( JPEGFName, "wb" );
        fwrite ( Buffer, 1, BufSizActual, JPEGFile );
        fclose ( JPEGFile );
        //
        //      Clean up and exit
        //
        avcodec_close ( pOCodecCtx );
        free ( Buffer );
        return ( BufSizActual );
}

I then tried invoking it with different values for the Quality
parameter, in three different ways:

1)
==
for ( QNo = 1; QNo < 100; QNo++ ) {
        //
        //      Encode the frame as a JPEG image                
        //
        Quality = (100 - QNo) * FF_LAMBDA_MAX / 100;
        WriteJPEG ( pCodecCtx, pFrame, i, Quality );
        }

2)
==
for ( QNo = 1; QNo < 32; QNo++ ) {
        //
        //      Encode the frame as a JPEG image                
        //
        WriteJPEG ( pCodecCtx, pFrame, i, QNo );
        }

3)
==
for ( QNo = 1; QNo < 150; QNo++ ) {
        //
        //      Encode the frame as a JPEG image                
        //
        WriteJPEG ( pCodecCtx, pFrame, i, QNo );
        }

1) produced all 100 files at 11556 bytes and no differences between them
(using fc to compare the files).

2) produced files 31 files (f008000q01.jpg - f008000q31.jpg), but with
sizes in the range 113,112 bytes (QNo = 1) to 17,381 bytes (QNo = 31)
and very different visual results - the compression artefacts were
getting much more visible as the size decreased QNo increased (ah ha,
we're getting somewhere)

3) In an attempt to see what the maximum value for QNo would be to
continue to affect the output, I first tried using 1 - FF_LAMBDA_MAX
(32767), but got bored after about 10,000 files were produced. So I cut
the limit down to 150 and tried again. I found decreasing output file
sizes in the range 1-128, after which the file did not change in size.
As before with QNo = 1 I got a file of 113,112 bytes and with 128 a file
of 11556 bytes (above 128, all files were 11556 - even when I allowed it
to output 10,000 files with changing QNo).

My conclusion is that setting qmin and qmax to a value between 1 and 128
gives us the variable quality, that the FF_LAMBDA_MAX value is a red
herring, that to scale the quality so it's expressed as a percentage
we'd use (100 - Qp) * 128 / 100.

It is necessary to close the codec when the required quality changes (or
as VOid pointed out when the height and/or width changes, too), and to
encode more than one frame at the same quality it is necessary to change
the pts value - whether it's just a simple increment I'm not sure.
That's how I changed it in previous experiments, and it seems reasonable
as the encoder would expect sequentially numbered frames.

So I will now finish off my frame extraction program, allowing the
customer to specify any number of frames from any number of video files
at any given quality and any given width/height. Luckily I've written it
as a scripting language for him, so all I need to do is collect all of
the same input file, quality and dimensions together and process those
in one hit, close the codec and so on.

Clive


-----Original Message-----
From: [email protected]
[mailto:[email protected]] On Behalf Of Stas Oskin
Sent: 02 April 2009 16:01
To: Libav* user questions and discussions
Subject: Re: [libav-user] Writing single frames to JPEG

2009/4/2 Clive Taylor <[email protected]>

> Oh yes. I also tried it with values in the range 1 & 31 and no
> difference.
>
> Clive
>

Ahh, so  Lambda really needs to be used it seems?
_______________________________________________
libav-user mailing list
[email protected]
https://lists.mplayerhq.hu/mailman/listinfo/libav-user


_______________________________________________
libav-user mailing list
[email protected]
https://lists.mplayerhq.hu/mailman/listinfo/libav-user

Reply via email to