1

I'm trying to create a C++ program that embeds FFMPEG functionality. To do that I took one FFMPEG example and recompiled it as is using g++ (only adding extern "C" where needed). Compilation is successful, but linking fails for undefined symbols.

cc -I/usr/include/x86_64-linux-gnu -Wall -g -fPIC  -c -o bin/decode_video.o decode_video.cpp

cc -Wl,--export-dynamic,--no-undefined -L /usr/lib/x86_64-linux-gnu/ -shared -lavdevice -lavutil -lavcodec  bin/decode_video.o -o decode_video.so

bin/decode_video.o: In function `decode(AVCodecContext*, AVFrame*, AVPacket*, char const*)':
/home/admin1/ffmpeg_examples/decode_video.cpp:60: undefined reference to `avcodec_send_packet'
/home/admin1/ffmpeg_examples/decode_video.cpp:67: undefined reference to `avcodec_receive_frame'
bin/decode_video.o: In function `main':
/home/admin1/ffmpeg_examples/decode_video.cpp:108: undefined reference to `av_packet_alloc'
/home/admin1/ffmpeg_examples/decode_video.cpp:116: undefined reference to `avcodec_find_decoder'
/home/admin1/ffmpeg_examples/decode_video.cpp:122: undefined reference to `av_parser_init'
/home/admin1/ffmpeg_examples/decode_video.cpp:128: undefined reference to `avcodec_alloc_context3'
/home/admin1/ffmpeg_examples/decode_video.cpp:139: undefined reference to `avcodec_open2'
/home/admin1/ffmpeg_examples/decode_video.cpp:150: undefined reference to `av_frame_alloc'
/home/admin1/ffmpeg_examples/decode_video.cpp:165: undefined reference to `av_parser_parse2'
/home/admin1/ffmpeg_examples/decode_video.cpp:184: undefined reference to `av_parser_close'
/home/admin1/ffmpeg_examples/decode_video.cpp:185: undefined reference to `avcodec_free_context'
/home/admin1/ffmpeg_examples/decode_video.cpp:186: undefined reference to `av_frame_free'
/home/admin1/ffmpeg_examples/decode_video.cpp:187: undefined reference to `av_packet_free'

checking nm I see that there's no name mangling in action so extern C seems to work well:

nm bin/decode_video.o 

                 U avcodec_alloc_context3
                 U avcodec_find_decoder
                 U avcodec_free_context
                 U avcodec_open2
                 U avcodec_receive_frame
                 U avcodec_send_packet
                 U av_frame_alloc
                 U av_frame_free
                 U av_packet_alloc
                 U av_packet_free
                 U av_parser_close
                 U av_parser_init
                 U av_parser_parse2
                 U exit
                 U fclose
                 U feof
                 U fflush
                 U fopen
                 U fprintf
                 U fread
                 U fwrite
                 U _GLOBAL_OFFSET_TABLE_
000000000000026f T main
                 U memset
                 U printf
                 U snprintf
                 U __stack_chk_fail
                 U stderr
                 U stdout
00000000000000a3 t _ZL6decodeP14AVCodecContextP7AVFrameP8AVPacketPKc
0000000000000000 t _ZL8pgm_savePhiiiPc

checking the library, for one of the 'missing' symbols, I can see it is implemented

nm /usr/lib/x86_64-linux-gnu/libavcodec.a | grep avcodec_send_packet

00000000000033e0 T avcodec_send_packet
                 U avcodec_send_packet

When compiling in C it works perfectly (without extern C of course) which makes me wonder what am I missing?

Below find FFMPEG example code for reference (decode_video.cpp):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern "C"
{
#include <libavcodec/avcodec.h>
}

#define INBUF_SIZE 4096

static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,
                     char *filename)
{
    FILE *f;
    int i;

    f = fopen(filename,"w");
    fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
    for (i = 0; i < ysize; i++)
        fwrite(buf + i * wrap, 1, xsize, f);
    fclose(f);
}

static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
                   const char *filename)
{
    char buf[1024];
    int ret;

    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0) {
        fprintf(stderr, "Error sending a packet for decoding\n");
        exit(1);
    }

    while (ret >= 0) {
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }

        printf("saving frame %3d\n", dec_ctx->frame_number);
        fflush(stdout);

        /* the picture is allocated by the decoder. no need to
           free it */
        snprintf(buf, sizeof(buf), "%s-%d", filename, dec_ctx->frame_number);
        pgm_save(frame->data[0], frame->linesize[0],
                 frame->width, frame->height, buf);
    }
}

int main(int argc, char **argv)
{
    const char *filename, *outfilename;
    const AVCodec *codec;
    AVCodecParserContext *parser;
    AVCodecContext *c= NULL;
    FILE *f;
    AVFrame *frame;
    uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data;
    size_t   data_size;
    int ret;
    AVPacket *pkt;

    if (argc <= 2) {
        fprintf(stderr, "Usage: %s <input file> <output file>\n"
                "And check your input file is encoded by mpeg1video please.\n", argv[0]);
        exit(0);
    }
    filename    = argv[1];
    outfilename = argv[2];

    pkt = av_packet_alloc();
    if (!pkt)
        exit(1);

    /* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
    memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);

    /* find the MPEG-1 video decoder */
    codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    parser = av_parser_init(codec->id);
    if (!parser) {
        fprintf(stderr, "parser not found\n");
        exit(1);
    }

    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

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

    /* open it */
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    f = fopen(filename, "rb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }

    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    while (!feof(f)) {
        /* read raw data from the input file */
        data_size = fread(inbuf, 1, INBUF_SIZE, f);
        if (!data_size)
            break;

        /* use the parser to split the data into frames */
        data = inbuf;
        while (data_size > 0) {
            ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
                                   data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
            if (ret < 0) {
                fprintf(stderr, "Error while parsing\n");
                exit(1);
            }
            data      += ret;
            data_size -= ret;

            if (pkt->size)
                decode(c, frame, pkt, outfilename);
        }
    }

    /* flush the decoder */
    decode(c, frame, NULL, outfilename);

    fclose(f);

    av_parser_close(parser);
    avcodec_free_context(&c);
    av_frame_free(&frame);
    av_packet_free(&pkt);

    return 0;
}
shayst
  • 267
  • 4
  • 14
  • 2
    Assuming you have the proper libs actually installed, just for kicks try putting your libs (`-lname`) *after* your object files on your build line. – WhozCraig Nov 11 '19 at 13:23
  • This actually works! Could you maybe post a detailed answer explaining the reasoning? – shayst Nov 11 '19 at 13:29
  • There are at least a dozen duplicates of this on this site alone, but I haven't the time to hunt them down. It comes down to the linker tossing out things that are unreferenced by prior-encountered code from left-to-right on the link line. I.e. a lib full of stuff that prior encountered libs and objs don't use will have those functions discarded, and thereby unavailable for use later. By moving them you're essentially telling the linker "when you see av_foo" you may not know what it is yet, but it's coming; I promise." then you eventually fulfill that promise with the `-lname` – WhozCraig Nov 11 '19 at 13:33
  • But I defined export-dynamic to specifically avoid this case assuming the final object will retain all symbols not only the ones used. Is my reasoning wrong? – shayst Nov 11 '19 at 13:36
  • That's for *your* library, not the library you're consuming. Btw, this is not universal. Not all linkers do this. gcc's is notorious for it, however. – WhozCraig Nov 11 '19 at 13:36
  • I see so that's where my confusion came from. I do recall there's a linker configuration that resolves this, I assumed it was this one. Can you explain the difference between C and C++ in this context? why C code compiled properly? After all, I took FFMPEG own example here – shayst Nov 11 '19 at 13:38
  • Just for anyone's reference, I rechecked the documentation. The proper definition to preserve the full symbol of a library, required to pass --whole-archive flag to linker (-Wl,--while-archive) That was my error. Thanks for pointing it out – shayst Nov 11 '19 at 13:46

1 Answers1

1

Thanks to @WhozCraig for helping me figure it out. I used the wrong flag when linking thinking I'm preserving the full library symbols, however it was the wrong flag. So linker did one pass and the objects order was wrong (since I naturally thought it didn't matter) To preserve the full library symbols pass --whole-archive to linker:

-Wl,--whole-archive
shayst
  • 267
  • 4
  • 14