6

The context : I have a succession of continuous bitmap and I want to encode them into a light video format. I use ffmpeg version 2.8.3 (the build here), under qt5, qt IDE, and msvc2013 for win32.

The problem : My code crash at sws_scale () (and sometimes at avcodec_encode_video2()). When I explore the stack, the crash event occurs at sws_getCachedContext (). (I can only see the stack with these ffmpeg builds). I only use these ffmpeg libraries (from the Qt .pro file) :

LIBS += -lavcodec -lavformat -lswscale -lavutil

It's swscale which bug. And this is the code :

void newVideo ()
{
    ULONG_PTR gdiplusToken;
    GdiplusStartupInput gdiplusStartupInput;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    initBitmap (); //init bmp
    int screenWidth =  bmp.bmiHeader.biWidth;
    int screenHeight = bmp.bmiHeader.biHeight;

    AVCodec * codec;
    AVCodecContext * c = NULL;
    uint8_t * outbuf;
    int i, out_size, outbuf_size;


    avcodec_register_all();

    qDebug () << "Video encoding\n";

    // Find the mpeg1 video encoder
    codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec)
    {
        qDebug () << "Codec not found\n";
        avcodec_close(c);
        av_free(c);
        return;
    }
    else
        qDebug () << "H264 codec found\n";

    c = avcodec_alloc_context3(codec);

    c->bit_rate = 1000000;
    c->width = 800; // resolution must be a multiple of two (1280x720),(1900x1080),(720x480)
    c->height = 600;
    c->time_base.num = 1; // framerate numerator
    c->time_base.den = 25; // framerate denominator
    c->gop_size = 30; // emit one intra frame every ten frames
    c->max_b_frames = 1; // maximum number of b-frames between non b-frames
    c->pix_fmt = AV_PIX_FMT_YUV420P; //Converstion RGB to YUV ?
    c->codec_id = AV_CODEC_ID_H264;

    struct SwsContext* fooContext = sws_getContext(screenWidth, screenHeight,
                                                   AV_PIX_FMT_RGB32,
                                                   c->width, c->height,
                                                   AV_PIX_FMT_YUV420P,
                                                   SWS_FAST_BILINEAR,
                                                   NULL, NULL, NULL);

    // Open the encoder
    if (avcodec_open2(c, codec, NULL) < 0)
    {
        qDebug () << "Could not open codec\n";
        avcodec_close(c);
        av_free(c);
        return;
    }
    else qDebug () << "H264 codec opened\n";

    outbuf_size = 100000 + c->width*c->height*(32>>3);//*(32>>3); // alloc image and output buffer
    outbuf = static_cast<uint8_t *>(malloc(outbuf_size));
    qDebug() << "Setting buffer size to: " << outbuf_size << "\n";

    FILE* f = fopen("TEST.mpg","wb");
    if(!f) qDebug() << "x - Cannot open video file for writing\n";
    else qDebug() << "Opened video file for writing\n";

    // encode 5 seconds of video
    for (i = 0; i < STREAM_FRAME_RATE*STREAM_DURATION; i++) //the stop condition i < 5.0*5
    {
        qDebug () << "i = " << i;
        fflush(stdout);

        HBITMAP hBmp;
        if (GetScreen(hBmp) == -1) return;
        BYTE * pPixels;// = new BYTE [bmp.bmiHeader.biSizeImage];
        pPixels = getPixels (hBmp);
        DeleteObject (hBmp);

        int nbytes = avpicture_get_size(AV_PIX_FMT_YUV420P, c->width, c->height);
        uint8_t* outbuffer = (uint8_t*)av_malloc(nbytes*sizeof(uint8_t));
        if(!outbuffer) // check if(outbuf) instead
        {
            qDebug () << "Bytes cannot be allocated";
            return;
        }

        AVFrame* inpic = avcodec_alloc_frame(); //av_frame_alloc () ?
        AVFrame* outpic = avcodec_alloc_frame();

        outpic->pts = (int64_t)((float)i * (1000.0/((float)(c->time_base.den))) * 90);
        if (avpicture_fill((AVPicture*) inpic, (uint8_t*) pPixels, AV_PIX_FMT_RGB32,
                       screenWidth, screenHeight) < 0)
            qDebug () <<  "avpicture_fill Fill picture with image failed"; //Fill picture with image

        if(avpicture_fill((AVPicture*) outpic, outbuffer, AV_PIX_FMT_YUV420P,
                       c->width, c->height) < 0)
            qDebug () <<  "avpicture_fill failed";

        if (av_image_alloc(outpic->data, outpic->linesize, c->width, c->height,
                       c->pix_fmt, 1) < 0)
            qDebug () <<  "av_image_alloc failed";

        inpic->data[0] += inpic->linesize[0]*(screenHeight - 1); // Flipping frame
        inpic->linesize[0] = -inpic->linesize[0]; // Flipping frame

////////////////////////////HERE THE BUG////////////////////////////////
        sws_scale(fooContext,
                  inpic->data, inpic->linesize,
                  0, c->height,
                  outpic->data, outpic->linesize); //HERE THE BUG

        av_free_packet((AVPacket *)outbuf);
        // encode the image
        out_size = avcodec_encode_video2 (c, (AVPacket *) outbuf,
                                          (AVFrame *) outbuf_size, (int *) outpic);
///////////////////////THE CODE DONT GO BEYOND/////////////////////////////////

        qDebug () << "Encoding frame" << i <<" (size=" << out_size <<"\n";
        fwrite(outbuf, 1, out_size, f);
        delete [] pPixels;
        av_free(outbuffer);
        av_free(inpic);
        av_freep(outpic);
    }

    // get the delayed frames
    for(; out_size; i++)
    {
        fflush(stdout);
        out_size = avcodec_encode_video2 (c, (AVPacket *) outbuf,
                                          (AVFrame *) outbuf_size, NULL);
        qDebug () << "Writing frame" << i <<" (size=" << out_size <<"\n";
        fwrite(outbuf, 1, out_size, f);
    }

    // add sequence end code to have a real mpeg file
    outbuf[0] = 0x00;
    outbuf[1] = 0x00;
    outbuf[2] = 0x01;
    outbuf[3] = 0xb7;
    fwrite(outbuf, 1, 4, f);
    fclose(f);

    avcodec_close(c);
    free(outbuf);
    av_free(c);
    qDebug () << "Closed codec and Freed\n";
}

And the output :

Video encoding

H264 codec found

H264 codec opened

Setting buffer size to:  2020000 

Opened video file for writing

i =  0
**CRASH**

I have thougth that my bitmap wasn't good so I have crafted a bitmap just for testing, the code was :

    uint8_t* pPixels = new uint8_t[Width * 3 * Height];
    int x = 50;
    for(unsigned int i = 0; i < Width * 3 * Height; i = i + 3) // loop for generating color changing images
    {
        pPixels [i] = x % 255; //R
        pPixels [i + 1] = (x) % 255; //G
        pPixels [i + 2] = (255 - x) % 255; //B
    }

However the crash continue. Perhaps, it might prove that it's not the bitmap (pPixels) which has a problem.

If anyone know, why I get this bug : Maybe don't I set one parameter well ? Or one ffmpeg deprecated function ? etc.


EDIT 1 27/12/15

Thanks to Ronald S. Bultje The function sws_scale () does not crash with this code, however I get an error from it bad dst image pointers. My code:

//DESTINATION FRAME            
if (avpicture_alloc ((AVPicture*) dst_frame, AV_PIX_FMT_YUV420P, c->width, c->height) < 0)
            {
                qDebug () <<  "# avpicture_alloc failed";
                return;
            }
            if(avpicture_fill((AVPicture*) dst_frame, NULL, AV_PIX_FMT_YUV420P,
                           c->width, c->height) < 0)
                qDebug () <<  "avpicture_fill failed";
            avcodec_align_dimensions2 (c, &c->width, &c->height, dst_frame->linesize);

//SOURCE FRAME
            if (avpicture_fill((AVPicture*) src_frame, (uint8_t *) pPixels, AV_PIX_FMT_RGB32,
                               tmp_screenWidth, tmp_screenHeight) < 0)
                qDebug () <<  "# avpicture_fill Fill picture with image failed"; //Fill picture with image
            avcodec_align_dimensions2 (c, &tmp_screenWidth, &tmp_screenHeight, src_frame->linesize);

            struct SwsContext* conversionContext = sws_getContext(tmp_screenWidth,tmp_screenHeight,AV_PIX_FMT_RGB32,c->width, c->height, AV_PIX_FMT_YUV420P,SWS_FAST_BILINEAR, NULL, NULL, NULL);

            int output_Height = sws_scale(conversionContext,
                                          src_frame->data, src_frame->linesize,
                                          0, tmp_screenHeight,
                                          dst_frame->data, dst_frame->linesize); //return 0 -> bad dst image pointers error

EDIT 2 28/12/15

I have tried to follow the Ronald S. Bultje's suggestion and now I get a bad src image pointers error, I have investigated and worked many hours but I do not find a solution. Here, there is the new snippet :

AVFrame* src_frame = av_frame_alloc ();
AVFrame* dst_frame = av_frame_alloc ();
AVFrame* tmp_src_frame = av_frame_alloc ();

/*........I do not use them until this snippet..........*/
//DESTINATION
//avpicture_free ((AVPicture*)dst_frame);
avcodec_align_dimensions2 (c, &c->width, &c->height, dst_frame->linesize);
if (avpicture_alloc ((AVPicture*) dst_frame, AV_PIX_FMT_YUV420P, c->width, c->height) < 0)
{
    qDebug () <<  "# avpicture_alloc failed";
    return;
}

//SOURCE
//stride = src_frame->linesize [0] = ((((screenWidth * bitPerPixel) + 31) & ~31) >> 3); do I need to do that ?
//== stride - I have gotten this formula from : https://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx
if (avpicture_fill((AVPicture*) src_frame, (uint8_t *) pPixels, AV_PIX_FMT_RGB32,
                   screenWidth, screenHeight) < 0)
    qDebug () <<  "# avpicture_fill Fill picture with image failed"; //Fill picture with image
//linesize [0] == 21760 like commented stride

//Source TO TMP Source
avcodec_align_dimensions2 (c, &tmp_screenWidth, &tmp_screenHeight, tmp_src_frame->linesize);
if (avpicture_fill((AVPicture*) tmp_src_frame, NULL, AV_PIX_FMT_RGB32,
                   tmp_screenWidth, tmp_screenHeight) < 0)
    qDebug () <<  "# avpicture_fill Fill picture with image failed"; //Fill picture with image

av_picture_copy ((AVPicture*) tmp_src_frame, (const AVPicture*) src_frame, AV_PIX_FMT_RGB32,
                 screenWidth, screenHeight);

struct SwsContext* conversionContext = sws_getContext(tmp_screenWidth, tmp_screenHeight,
                                                      AV_PIX_FMT_RGB32,
                                                      c->width, c->height,
                                                      AV_PIX_FMT_YUV420P,
                                                      SWS_FAST_BILINEAR,
                                                      NULL, NULL, NULL);

int output_Height = sws_scale(conversionContext,
                              tmp_src_frame->data, tmp_src_frame->linesize,
                              0, tmp_screenHeight,
                              dst_frame->data, dst_frame->linesize);
//ffmpeg error = bad src image pointers
// output_Height == 0

EDIT 3

For temp Picture I have done an avcode_align_dimension2() then a avpicture_alloc() for allocating memory and avpicture_fill() in order to fill the picture pointer. Below the updated code:

//DESTINATION
//avpicture_free ((AVPicture*)dst_frame);
avcodec_align_dimensions2 (c, &c->width, &c->height, dst_frame->linesize);
if (avpicture_alloc ((AVPicture*) dst_frame, AV_PIX_FMT_YUV420P, c->width, c->height) < 0)
{
    qDebug () <<  "# avpicture_alloc failed";
    return;
}

//SOURCE
//src_frame->linesize [0] = ((((screenWidth * bpp) + 31) & ~31) >> 3);
//src_frame->linesize [0] = stride;
if (avpicture_fill((AVPicture*) src_frame, (uint8_t *) pPixels, AV_PIX_FMT_RGB32,
                   screenWidth, screenHeight) < 0)
    qDebug () <<  "# avpicture_fill Fill picture with image failed"; //Fill picture with image

//Source TO TMP Source
avcodec_align_dimensions2 (c, &tmp_screenWidth, &tmp_screenHeight, tmp_src_frame->linesize);
if (avpicture_alloc ((AVPicture*) tmp_src_frame, AV_PIX_FMT_RGB32, tmp_screenWidth, tmp_screenHeight) < 0)
{
    qDebug () <<  "# avpicture_alloc failed";
    return;
}
int outbuf_size = tmp_screenWidth*tmp_screenHeight*4;// alloc image and output buffer
outbuf = static_cast<uint8_t *>(malloc(outbuf_size));
if (avpicture_fill((AVPicture*) tmp_src_frame, outbuf, AV_PIX_FMT_RGB32,
                   tmp_screenWidth, tmp_screenHeight) < 0)
    qDebug () <<  "# avpicture_fill Fill picture with image failed"; //Fill picture with image
av_picture_copy ((AVPicture*) tmp_src_frame, (const AVPicture*) src_frame, AV_PIX_FMT_RGB32,
                 tmp_screenWidth, tmp_screenHeight);

struct SwsContext* conversionContext = sws_getContext(tmp_screenWidth, tmp_screenHeight,
                                                      AV_PIX_FMT_RGB32,
                                                      c->width, c->height,
                                                      AV_PIX_FMT_YUV420P,
                                                      SWS_FAST_BILINEAR,
                                                      NULL, NULL, NULL);

int output_Height = sws_scale(conversionContext,
                              tmp_src_frame->data, tmp_src_frame->linesize,
                              0, tmp_screenHeight,
                              dst_frame->data, dst_frame->linesize);

The call stack is as follow : av_picture_copy() is called then av_image_copy() then _VEC_memcpy() then fastcopy_I() and crash ... The problem is not the dimensions (tmp_screenWidth/Height) ? (With av_picture_copy () could we copy a picture P1 with dim W1xH1 to a picture P2 with dimension W2xH2 ?)

EDIT 4

Crash at av_picture_copy() which call _aligned_malloc() then av_image_copy _VEC_memcpy() and fastcopy_I()

//SOURCE
if (avpicture_fill((AVPicture*) src_frame, (uint8_t *) pPixels, AV_PIX_FMT_RGB32,
                   screenWidth, screenHeight) < 0)
    qDebug () <<  "# avpicture_fill Fill picture with image failed"; //Fill picture with image

//Source TO TMP Source
avcodec_align_dimensions2 (c, &tmp_screenWidth, &tmp_screenHeight, tmp_src_frame->linesize);
if (avpicture_alloc ((AVPicture*) tmp_src_frame, AV_PIX_FMT_RGB32, tmp_screenWidth, tmp_screenHeight) < 0)
{
    qDebug () <<  "# avpicture_alloc failed";
    return;
}
av_picture_copy ((AVPicture*) tmp_src_frame, (const AVPicture*) src_frame, AV_PIX_FMT_RGB32,
                 tmp_screenWidth, tmp_screenHeight);
Al Bundy
  • 184
  • 1
  • 12
  • IDK why your code is crashing, but why use a fixed bitrate instead of CRF, and why such a tiny GOP size? From the command line, `-preset slow -crf 20` should give good quality. IDK where to start debugging, since you didn't post a backtrace showing what the function arguments were to the function that eventually segfaulted, let alone the call tree. – Peter Cordes Dec 24 '15 at 09:52
  • @PeterCordes : It is not how my final code will be, it is just a first draft where I put the main lines and I will reequilibrate after that. I just want to see if the ffmpeg mecanic work. Nevertheless it crashs at _swscale()_. I cannot explore into the ffmpeg code with this ffmpeg build (there is only release library build on ffmpeg website), I can only see the stack with the ffmpeg function which crash and that my problem ... An exception is trigerred. The stack is : _swscale() -> RtlValidateHeap() (ntdll File) ->sws_get_class () (swscale_3 File)-> sws_getCachedContext () and Crash_ – Al Bundy Dec 24 '15 at 10:41

1 Answers1

4

You're using avpicture_fill(), which is implemented like this:

int avpicture_fill(AVPicture *picture, const uint8_t *ptr,
                   enum AVPixelFormat pix_fmt, int width, int height)
{
    return av_image_fill_arrays(picture->data, picture->linesize,
                                ptr, pix_fmt, width, height, 1);
}

Note the last argument to av_image_fill_arrays(), align=1. This means buffer lines will be unaligned. Unfortunately, and this isn't clear at all in the documentation, most FFmpeg functions require buffer lines to be aligned to a power-of-two that allows SSE2 or AVX2 optimizations, e.g. align=32. See the second bullet point in this response on how to do that programmatically.

Aside, in your test code, you're using new (instead of av_malloc) to allocate memory, and the returned pointer from new is also not guaranteed to be aligned by 32 byte.

Community
  • 1
  • 1
Ronald S. Bultje
  • 10,828
  • 26
  • 47
  • sws_scale works if I add : `avcodec_align_dimensions2 (c, &c->width, &c->height, inpic->linesize);` and I change the `new` by `av_malloc` casted in `uint8_t`. Now the function `avcodec_encode_video2 (c, (AVPacket *) outbuf,(AVFrame *) outbuf_size, (int *) outpic);` called just after sws_scale trigerred an exception. Im going to check outbuf, I think it is not well allocated. – Al Bundy Dec 24 '15 at 15:27
  • Now it "works" I have forgotten to add a `avcodec_align_dimensions2 (c, &c->width, &c->height, outpic->linesize);` just after `swscale()` call. Nonetheless, I loop 8 times before a crash with this output `Encoding frame X (size= -542398533 )`. On the stack it's always `sws_get_CachedContext ()` which triggered an exception. (Otherwise, I think that `outbuf` is well allocated). – Al Bundy Dec 24 '15 at 16:00
  • I would run that through valgrind or address sanitizer... Also you'll want to post your updated code. – Ronald S. Bultje Dec 24 '15 at 21:52
  • I have just edited my post and updated a snippet with your advides. I get an error *bad dst image pointers* from `sws_scale ()`. – Al Bundy Dec 27 '15 at 08:44
  • 1
    Ah, I see, your order of operations is inverted. Don't do alloc+fill+align for the dest picture, but do align+alloc (in that order). Likewise, for the source, don't use align unless FFmpeg allocated the data. If it did, use align+fill. Else, use fill with the linesizes of your original data. If that is not aligned to 32 pixels by itself, you need to fill the source and align+fill a temp image and av_picture_copy/av_image_copy between the two before handing tmp as source to sws_scale. The error is because fill resets the data pointers to NULL in dst after av_picture_alloc. – Ronald S. Bultje Dec 27 '15 at 12:17
  • I have just edited the code with your advices but I have gotten a new error *bad src image pointers*. It is the Edit 2 28/12/2015 above. – Al Bundy Dec 28 '15 at 12:13
  • You're filling the temp picture with a NULL data argument. You need to allocate that temp picture also (instead of filling it with NULL). – Ronald S. Bultje Dec 28 '15 at 13:03
  • I have done `avpicture_fill` on the source with the datas then, `avcodec_align_dimensions2`+`avpicture_alloc` (without `avpicture_fill ()`) on temp picture (*tmp_src_frame*) then `av_picture_copy`. But it does not work ... `av_picture_copy` crash – Al Bundy Dec 28 '15 at 13:54
  • ... I `avpicture_alloc()` on temp Picture is enough because I `av_picture_copy()` just after with the source picture, am I right ? Moreover the dimensions are differents : For the source Picture it is *screenWidth/Height* and for the temp Picture it the same ajusted by `avcodec_align_dimension2()` (*tmp_screenWidht/Height*) and I call copy on the last one ... the error, is it here ? – Al Bundy Dec 28 '15 at 14:05
  • Always show your latest code. For crashes, also show the backtrace. – Ronald S. Bultje Dec 28 '15 at 14:52
  • Remove the malloc and fill for the tmp image, you already used avpicture_alloc to allocate/fill, no need to do it again manually (this is almost certainly the cause of your crash). – Ronald S. Bultje Dec 28 '15 at 19:08
  • you'll have to debug this, I really don't have any useful information. It might be that tmp_screenWidth/Height are used uninitialized or are modified from screenWidth/Height (which you should use in copy, not tmp counterparts). But really, it's hard to say without any information on my end. Try to debug this like you would debug any other crash. – Ronald S. Bultje Dec 28 '15 at 20:17