1

Trying to read .mkv file and write it to .bmp, but resulting .bmp is black-and-white and consists of multiple mini-images of what supposed to be written:

int main()
{
    av_register_all();
    avformat_network_init();
    avfilter_register_all();

    //crashes on -Ofast without =NULL initialization:
    AVFormatContext * format = NULL;
    if ( avformat_open_input( & format, VIDEO_FILE, NULL, NULL ) != 0 ) {
        cerr << "Could not open file " << VIDEO_FILE << endl;
        return -1;
    }

    // Retrieve stream information
    if ( avformat_find_stream_info( format, NULL ) < 0) {
        cerr << "avformat_find_stream_info() failed." << endl;
        return -1;
    }
    av_dump_format( format, 0, VIDEO_FILE, false );

    AVCodec * video_dec = (AVCodec*)1;
    AVCodec * audio_dec = (AVCodec*)1;
    const auto video_stream_index = av_find_best_stream( format, AVMEDIA_TYPE_VIDEO, -1, -1, & video_dec, 0 );
    const auto audio_stream_index = av_find_best_stream( format, AVMEDIA_TYPE_AUDIO, -1, -1, & audio_dec, 0 );
    if ( video_stream_index < 0 ) {
        cerr << "Failed to find video stream." << endl;
        return -1;
    }
    if ( audio_stream_index < 0 ) {
        cerr << "Failed to find audio stream." << endl;
        return -1;
    }

    AVCodecParameters * videoParams = format->streams[ video_stream_index ]->codecpar;
    cout << "Having " << videoParams->width << " | " << videoParams->height << " video." << endl;

    av_read_play( format );

    // create decoding context
    AVCodecContext * video_ctx = avcodec_alloc_context3( video_dec );
    AVCodecContext * audio_ctx = avcodec_alloc_context3( audio_dec );
    if ( ! video_ctx || ! audio_ctx ) {
        cerr << "Failed to avcodec_alloc_context3()" << endl;
        return -1;
    }
    if ( video_dec->capabilities & AV_CODEC_CAP_TRUNCATED ) video_ctx->flags |= AV_CODEC_FLAG_TRUNCATED; // we do not send complete frames

    /* For some codecs, such as msmpeg4 and mpeg4, width and height
       MUST be initialized there because this information is not
       available in the bitstream. */

    avcodec_parameters_to_context( video_ctx, format->streams[ video_stream_index ]->codecpar );
    avcodec_parameters_to_context( audio_ctx, format->streams[ audio_stream_index ]->codecpar );
    if ( avcodec_open2( video_ctx, video_dec, NULL ) < 0 ) {
        cout << "Failed to open video decoder." << endl;
        return -1;
    }
    if ( avcodec_open2( audio_ctx, audio_dec, NULL ) < 0 ) {
        cout << "Failed to open audio decoder." << endl;
        return -1;
    }

    uint8_t* picture_buffer = (uint8_t*) (av_malloc( avpicture_get_size( AV_PIX_FMT_RGB24 , videoParams->width, videoParams->height ) ));
    AVFrame* picture = av_frame_alloc();
    avpicture_fill( (AVPicture *) picture, picture_buffer, AV_PIX_FMT_RGB24, video_ctx->width, video_ctx->height );

    AVPacket packet;
    av_init_packet( & packet );

    int cnt = 0;
    while ( av_read_frame( format, & packet ) >= 0 && cnt < 10 ) {
        if ( packet.stream_index == video_stream_index ) {
            int check;
            const auto result = avcodec_decode_video2( video_ctx, picture, & check, & packet );
            cout << "Bytes decoded " << result << " check " << check << endl;

            std::string name = "debug/av/";
            name += std::to_string( cnt ) + ".bmp";
            cout << "Writing frame " << name << " with linesize " << picture->linesize[0] << " ..." << endl;
            write_bmp( (uint8_t*) picture->data, videoParams->width, videoParams->height, name.c_str() );

            av_frame_unref( picture );

            ++ cnt;
        }
        else if ( packet.stream_index == audio_stream_index ) {
            cout << "Sound packet" << endl;
        }
        av_free_packet( & packet );
        av_init_packet( & packet );
    }
}

How can I fix it?

Slaus
  • 2,086
  • 4
  • 26
  • 41

1 Answers1

1

You must convert the picture to RGB with swscale.

szatmary
  • 29,969
  • 8
  • 44
  • 57
  • Thank you for your answer! As I understand it properly, image is in **AV_PIX_FMT_YUV420P** format, but how exactly can I convert it to RGB? Is there a way to programmatically determine frame format so that *any* video file would work? – Slaus Mar 10 '20 at 14:52
  • 1
    Yes, look at the documentation for AVFrame. it has a format value you can pass to swscale. swscale is also very well documented with many examples on the internet. – szatmary Mar 10 '20 at 16:09
  • Nah, I tried to use about ~10 examples of **libav** and all failed mostly due to my **Ubuntu 18.04 and kernel v5.4** **libav** being too modern I guess. For example, with [this sample](https://libav.org/documentation/doxygen/master/decode__video_8c_source.html) I got furthest: it at least compiles, but gives `[mpeg1video @ 0x55ded11f97e0] Invalid frame dimensions 0x0.` error :( – Slaus Mar 10 '20 at 16:23
  • So, currently trying to adapt samples from [here](https://stackoverflow.com/questions/49068812/swscaler-bad-src-image-pointers) and [here](https://stackoverflow.com/questions/12831761/how-to-resize-a-picture-using-ffmpegs-sws-scale) to construct **AVFrame** for **swsscale()** but getting `swscaler: bad src image pointers` error. – Slaus Mar 10 '20 at 16:59
  • ... and another attempt (can't remember where I found it) to initialize **AVFrame** with utility function `av_image_alloc()` [like this](https://paste.ubuntu.com/p/zrBMrDJkfd/) fails right inside `if()`. – Slaus Mar 10 '20 at 17:03
  • [This](https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/decode_video.c) example fails with `Codec not found` error; I tried prepending it with `avcodec_register_all();` and now it fails with `[mpeg1video @ 0x55d46c240aa0] Invalid frame dimensions 0x0.` error. [Someone](http://www.ffmpeg-archive.org/mpeg1video-0x7fb9ae801800-Invalid-frame-dimensions-0x0-tt4687429.html) had the same issue, but no solutions for a year ... – Slaus Mar 10 '20 at 18:00
  • The examples you are posting don't even use swscale. google "swscale" and click on the doxygen link. – szatmary Mar 10 '20 at 21:42
  • Some of them don't use, yes, but I was trying to just get frames out of video (which is my original goal) with whatever method possible. **swscale** docs are straightforward, but currently the problem is in providing it **AVFrame**s, so far I tried doing it [in 3 different ways](https://paste.ubuntu.com/p/KdGjvV27fT/). Currently I temporarely gave up on it and getting frames by invoking `ffmpeg -i ./my_video.mkv -pix_fmt bgr24 -ss 00:00:20 -vframes 10 ./debug/ffmpeg/out_%03d.bmp` and reading them as plain **.bmp** files which looks clumsy ^^ – Slaus Mar 11 '20 at 05:12