Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom input #31

Closed
ChillyWillyGuru opened this issue Jun 16, 2018 · 14 comments
Closed

Custom input #31

ChillyWillyGuru opened this issue Jun 16, 2018 · 14 comments

Comments

@ChillyWillyGuru
Copy link

The current library will create a source from a URL, but what would be handy is another function that created a source using custom read and seek routines. Looking at the source for Kit_CreateSourceFromUrl and at examples of creating a custom ffmpeg io context (like this one: https://www.codeproject.com/Tips/489450/Creating-Custom-FFmpeg-IO-Context ), it seems like that should be fairly easy. This would be a major boon to people doing things like reading files through PHYSFS or the like.

@katajakasa
Copy link
Owner

Yes, this is indeed on my TODO list for version 1.0.0. Currenly under construction, in fact.

@ChillyWillyGuru
Copy link
Author

Great! Thanks a bunch.

@ChillyWillyGuru
Copy link
Author

ChillyWillyGuru commented Jun 20, 2018

I made a function to create a source with a custom IO. Seems to work well. Feel free to use this or ignore as you see fit. :)

Changes...
In kitsource.h

typedef struct Kit_Source {
    int audio_stream_index;     ///< Audio stream index
    int video_stream_index;     ///< Video stream index
    int subtitle_stream_index;  ///< Subtitle stream index
    void *format_ctx;           ///< FFmpeg: Videostream format context
    unsigned char *read_buffer; ///< custom IO read buffer
} Kit_Source;

and

KIT_API Kit_Source* Kit_CreateSourceFromUrl(const char *path);
KIT_API Kit_Source* Kit_CreateSourceFromCio(int (*ReadFunc)(void*, unsigned char*, int),
        int64_t (*SeekFunc)(void*, int64_t, int), void *UserPtr, int BufferSize);
KIT_API void Kit_CloseSource(Kit_Source *src);

In kitsource.c

Kit_Source* Kit_CreateSourceFromCio(int (*ReadFunc)(void*, unsigned char*, int),
    int64_t (*SeekFunc)(void*, int64_t, int), void *UserPtr, int BufferSize) {
    assert(ReadFunc != NULL && SeekFunc != NULL);

    Kit_Source *src = calloc(1, sizeof(Kit_Source));
    if(src == NULL) {
        Kit_SetError("Unable to allocate source");
        return NULL;
    }

    src->read_buffer = calloc(1, BufferSize);
    if(src->read_buffer == NULL) {
        Kit_SetError("Unable to allocate read buffer");
        free(src);
        return NULL;
    }

    // Allocate an AVIOContext
    AVIOContext *pIOCtx = avio_alloc_context(src->read_buffer, BufferSize,
                                        0, UserPtr, ReadFunc, 0, SeekFunc);

    // Allocate an AVFormatContext
    AVFormatContext *fmt = avformat_alloc_context();
    src->format_ctx = (void*)fmt;

    // Set the IOContext
    fmt->pb = pIOCtx;

    // Get part of input to determine format
    int ReadBytes = ReadFunc(UserPtr, src->read_buffer, BufferSize);
    if(ReadBytes <= 0) {
        Kit_SetError("Unable to read input");
        free(src->read_buffer);
        free(src);
        return NULL;
    }

    // Reset the input to beginning
    SeekFunc(UserPtr, 0, SEEK_SET);

    // Set ProbeData-structure for av_probe_input_format
    AVProbeData probeData;
    probeData.buf = src->read_buffer;
    probeData.buf_size = ReadBytes;
    probeData.filename = "";

    // Determine the input format
    fmt->iformat = av_probe_input_format(&probeData, 1);

    fmt->flags = AVFMT_FLAG_CUSTOM_IO;

    // Attempt to open source
    if(avformat_open_input((AVFormatContext **)&src->format_ctx, "", NULL, NULL) < 0) {
        Kit_SetError("Unable to open source with cio");
        goto exit_0;
    }

    av_opt_set_int(src->format_ctx, "probesize", INT_MAX, 0);
    av_opt_set_int(src->format_ctx, "analyzeduration", INT_MAX, 0);

    // Fetch stream information. This may potentially take a while.
    if(avformat_find_stream_info((AVFormatContext *)src->format_ctx, NULL) < 0) {
        Kit_SetError("Unable to fetch source information");
        goto exit_1;
    }

    // Find best streams for defaults
    src->audio_stream_index = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO);
    src->video_stream_index = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO);
    src->subtitle_stream_index = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE);
    return src;

exit_1:
    avformat_close_input((AVFormatContext **)&src->format_ctx);
    av_free(pIOCtx);
exit_0:
    free(src->read_buffer);
    free(src);
    return NULL;
}

void Kit_CloseSource(Kit_Source *src) {
    assert(src != NULL);

    AVFormatContext *fmt = (AVFormatContext*)src->format_ctx;

    if(fmt->flags != AVFMT_FLAG_CUSTOM_IO) {
        avformat_close_input((AVFormatContext **)&src->format_ctx);
        free(src);
        return;
    }

    AVIOContext *pIOCtx = fmt->pb;
    avformat_close_input((AVFormatContext **)&src->format_ctx);
    av_free(pIOCtx);
    free(src->read_buffer);
    free(src);
    return;
}

and Bob's your uncle! :)

@katajakasa
Copy link
Owner

Well, I finally got to it and cleaned up my custom source branch, it should now be in master.

I was wondering, is there a reason why you scan the incoming stream yourself before opening? I couldn't find any documentation about that (ffmpeg docs being what they are :( )

@ChillyWillyGuru
Copy link
Author

The example I worked from said that libav will scan up to 5MB of the file unless you do it yourself, so unless you allocate a 5MB buffer, it fails by overwriting memory. Now maybe that got fixed since the example was made. If so, then all the code can be removed.

Oh, and a friend of mine was trying to compile this in MSVC and found that when passing the functions, the int64_t in the h file is not accepted. She had to change them to long long to get kitchensink to compile. Weird, huh?

@ChillyWillyGuru
Copy link
Author

ChillyWillyGuru commented Jun 21, 2018

OKay, I checked out the new code... nice work on the custom input! Really clean and easy to follow. However, I'm getting an exception from Kit_CloseSource()...

double free or corruption (!prev)

Thread 1 "EDGE" received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
51	../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  0x00007ffff5c99e97 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1  0x00007ffff5c9b801 in __GI_abort () at abort.c:79
#2  0x00007ffff5ce4897 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7ffff5e11b9a "%s\n")
    at ../sysdeps/posix/libc_fatal.c:181
#3  0x00007ffff5ceb90a in malloc_printerr (str=str@entry=0x7ffff5e13890 "double free or corruption (!prev)") at malloc.c:5350
#4  0x00007ffff5cf2e84 in _int_free (have_lock=0, p=0x555556a06950, av=0x7ffff6046c40 <main_arena>) at malloc.c:4281
#5  0x00007ffff5cf2e84 in __GI___libc_free (mem=0x555556a06960) at malloc.c:3124
#6  0x00007ffff6b99e8f in Kit_CloseSource () at /usr/local/lib/libSDL_kitchensink.so.0
#7  0x0000555555671f83 in CIN_StopCinematic(int) ()
#8  0x00005555556719ec in E_PlayMovie(char const*, int) ()
#9  0x0000555555687915 in E_Main(int, char const**) ()
#10 0x000055555566c449 in main ()

Apparently, one of the memory blocks getting freed got walked on... maybe that issue with checking the file I mention above? Maybe that's not fixed yet.

@katajakasa
Copy link
Owner

Aaha, for some reason I couldn't get this to happen on Windows but happened first time on Ubuntu :)

Alright, looks like avformat_close_input does all the frees when it's called, so extra free calls are not needed. I committed a fix to master for this, did not yet run valgrind or clean up the code.

And yeah... when it comes to MSVC, MS does not guarantee C99 compliancy :(

@ChillyWillyGuru
Copy link
Author

Yeah, it'd be great to see them meet a standard instead of trying to get what they do called a standard. That's just lazy programming... "I can't fix these bugs, so let's make a new standard where the bugs ARE standard!" It's good that you caught that. That must be a change in libav as all of the (older) examples I've looked at do the frees manually. The dangers of going by old example code... Well, I'll pull the latest and try it out.

@ChillyWillyGuru
Copy link
Author

Working great! I think that's another issue resolved. :)

@ChillyWillyGuru
Copy link
Author

Actually, I think there might still be an issue with the video probing walking on memory. If I try some arbitrary mp4, I get this:

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x55ac63e38520] moov atom not found
double free or corruption (!prev)

But if I bump the buffer to 8MB, I only get this:
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x55ac63e38520] moov atom not found

I think those extra free()s are needed, and simply not doing them suppressed the exception in the case of play the mkv I was testing before.

@katajakasa
Copy link
Owner

katajakasa commented Jun 21, 2018

On the other hand, I don't get any memory leaks in valgrind when I leave the extra free calls out, which in my mind says that they /shouldn't/ be needed :/

Okay, a shot in the dark: what happens if you comment out these from Kit_CreateSourceFromCustom?

    // Set probe opts for input scanning
    av_opt_set_int(src->format_ctx, "probesize", INT_MAX, 0);
    av_opt_set_int(src->format_ctx, "analyzeduration", INT_MAX, 0);

Edit: Actually, never mind, I'm stupid. That's buggy code, src->format_ctx is not yet set >_<

@katajakasa
Copy link
Owner

katajakasa commented Jun 21, 2018

Okay, so I realized there is this: https://www.ffmpeg.org/doxygen/3.4/avio_reading_8c-example.html so then I implemented this: 62bdf31

At least doesn't crash on my computer and I can't get anything to die horribly in gdb :/

@ChillyWillyGuru
Copy link
Author

Well, I'll call it good until I can find something traceable. :) It works properly on the project I'm using it in.

@katajakasa
Copy link
Owner

Okay. I couldn't get those errors with any of the videos I tried :/

I'll close this ticket as resolved, and if you run into those problems again, just open up a new one :)

Thanks for comments and testing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants