Yun Tao Hai
Yun Tao Hai

Reputation: 131

JavaCV: avformat_open_input() hangs (not network, but with custom AVIOContext)

I'm using a custom AVIOContext to bridge FFMpeg with java IO. The function avformat_open_input() never returns. I have searched the web for similar problems, all of which were caused by faulty network or wrong server configurations. However, I'm not using network at all, as you can see in the following little program:

package com.example;

import org.bytedeco.javacpp.*;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import static org.bytedeco.javacpp.avcodec.*;
import static org.bytedeco.javacpp.avformat.*;
import static org.bytedeco.javacpp.avutil.*;
import static org.bytedeco.javacpp.avdevice.*;
import static org.bytedeco.javacpp.avformat.AVFormatContext.*;

public class Test {

    public static void main(String[] args) throws Exception {
        File dir = new File(System.getProperty("user.home"), "Desktop");
        File file = new File(dir, "sample.3gp");
        final RandomAccessFile raf = new RandomAccessFile(file, "r");

        Loader.load(avcodec.class);
        Loader.load(avformat.class);
        Loader.load(avutil.class);
        Loader.load(avdevice.class);
        Loader.load(swscale.class);
        Loader.load(swresample.class);

        avcodec_register_all();
        av_register_all();
        avformat_network_init();
        avdevice_register_all();

        Read_packet_Pointer_BytePointer_int reader = new Read_packet_Pointer_BytePointer_int() {
            @Override
            public int call(Pointer pointer, BytePointer buf, int bufSize) {
                try {
                    byte[] data = new byte[bufSize]; // this is inefficient, just use as a quick example
                    int read = raf.read(data);

                    if (read <= 0) {
                        System.out.println("EOF found.");
                        return AVERROR_EOF;
                    }

                    System.out.println("Successfully read " + read + " bytes of data.");
                    buf.position(0);
                    buf.put(data, 0, read);
                    return read;
                } catch (Exception ex) {
                    ex.printStackTrace();
                    return -1;
                }
            }
        };

        Seek_Pointer_long_int seeker = new Seek_Pointer_long_int() {
            @Override
            public long call(Pointer pointer, long offset, int whence) {
                try {
                    raf.seek(offset);
                    System.out.println("Successfully seeked to position " + offset + ".");
                    return offset;
                } catch (IOException ex) {
                    return -1;
                }
            }
        };

        int inputBufferSize = 32768;
        BytePointer inputBuffer = new BytePointer(av_malloc(inputBufferSize));
        AVIOContext ioContext = avio_alloc_context(inputBuffer, inputBufferSize, 1, null, reader, null, seeker);
        AVInputFormat format = av_find_input_format("3gp");
        AVFormatContext formatContext = avformat_alloc_context();
        formatContext.iformat(format);
        formatContext.flags(formatContext.flags() | AVFMT_FLAG_CUSTOM_IO);
        formatContext.pb(ioContext);

        // This never returns. And I can never get result.
        int result = avformat_open_input(formatContext, "", format, null);

        // all clean-up code omitted for simplicity
    }

}

And below is my sample console output:

Successfully read 32768 bytes of data.
Successfully read 32768 bytes of data.
Successfully read 32768 bytes of data.
Successfully read 32768 bytes of data.
Successfully read 32768 bytes of data.
Successfully read 7240 bytes of data.
EOF found.

I've checked the sum of bytes, which corresponds to the file size; EOF is also hit, meaning the file is completely read. Actually I am a bit skeptical as why avformat_open_input() would even read the entire file and still without returning? There must be something wrong with what I am doing. Can any expert shed some lights or point me to the right direction? I'm new to javacv and ffmpeg and especially to programming with Buffers and stuff. Any help, suggestion or criticism is welcome. Thanks in advance.

Upvotes: 1

Views: 2055

Answers (2)

Yun Tao Hai
Yun Tao Hai

Reputation: 131

Ok, now I have found the problem. I have misinterpreted the docs and overlooked most of the examples I found. My bad.

According to the documentation on ffmpeg:

AVIOContext* avio_alloc_context (unsigned char* buffer,
                                 int            buffer_size,
                                 int            write_flag,
                                 void*          opaque,
                                 int(*)(void *opaque, uint8_t *buf, int buf_size)     read_packet,
                                 int(*)(void *opaque, uint8_t *buf, int buf_size)     write_packet,
                                 int64_t(*)(void *opaque, int64_t offset, int whence) seek 
)

The third parameter, write_flag is used in the following fashion:

write_flag - Set to 1 if the buffer should be writable, 0 otherwise.

Actually, it means if the AVIOContext is for data output (i.e. writing), write_flag should be set to 1. Otherwise, if the context is for data input (i.e. reading), it should be set to 0.

In the question I posted, I passed 1 as the write_flag and it is causing the problem when reading. Passing 0 instead solves the problem.

Later I re-read all the examples I found, all the avio_alloc_context() calls uses 0, not 1 when reading. So that further indicates why I'm having the problem.

To conclude, I will post the revised code with the problems corrected as a future reference.

package com.example;

import org.bytedeco.javacpp.*;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

import static org.bytedeco.javacpp.avformat.*;
import static org.bytedeco.javacpp.avutil.*;
import static org.bytedeco.javacpp.avformat.AVFormatContext.*;

public class Test {

    public static void main(String[] args) throws Exception {
        File dir = new File(System.getProperty("user.home"), "Desktop");
        File file = new File(dir, "sample.3gp");
        final RandomAccessFile raf = new RandomAccessFile(file, "r");

        Loader.load(avformat.class);
        Loader.load(avutil.class);

        av_register_all();
        avformat_network_init();

        Read_packet_Pointer_BytePointer_int reader = new Read_packet_Pointer_BytePointer_int() {
            @Override
            public int call(Pointer pointer, BytePointer buf, int bufSize) {
                try {
                    byte[] data = new byte[bufSize]; // this is inefficient, just use as a quick example
                    int read = raf.read(data);

                    if (read <= 0) {
                        // I am still unsure as to return '0', '-1' or 'AVERROR_EOF'.
                        // But according to the following link, it should return 'AVERROR_EOF',
                        // http://www.codeproject.com/Tips/489450/Creating-Custom-FFmpeg-IO-Context
                        // btw 'AVERROR_EOF' is a nasty negative number, '-541478725'.
                        return AVERROR_EOF;
                    }

                    buf.position(0);
                    buf.put(data, 0, read);
                    return read;
                } catch (Exception ex) {
                    ex.printStackTrace();
                    return -1;
                }
            }
        };

        Seek_Pointer_long_int seeker = new Seek_Pointer_long_int() {
            @Override
            public long call(Pointer pointer, long offset, int whence) {
                try {
                    if (whence == AVSEEK_SIZE) {
                        // Returns the entire file length. If not supported, simply returns a negative number.
                        // https://www.ffmpeg.org/doxygen/trunk/avio_8h.html#a427ff2a881637b47ee7d7f9e368be63f
                        return raf.length();
                    }

                    raf.seek(offset);
                    return offset;
                } catch (IOException ex) {
                    ex.printStackTrace();
                    return -1;
                }
            }
        };

        int inputBufferSize = 32768;
        BytePointer inputBuffer = new BytePointer(av_malloc(inputBufferSize));

        AVIOContext ioContext = avio_alloc_context(inputBuffer,
                                                   inputBufferSize,
                                                   0, // CRITICAL, if the context is for reading, it should be ZERO
                                                      //           if the context is for writing, then it is ONE
                                                   null,
                                                   reader,
                                                   null,
                                                   seeker);

        AVInputFormat format = av_find_input_format("3gp");
        AVFormatContext formatContext = avformat_alloc_context();
        formatContext.iformat(format);
        formatContext.flags(formatContext.flags() | AVFMT_FLAG_CUSTOM_IO);
        formatContext.pb(ioContext);

        // Now this is working properly.
        int result = avformat_open_input(formatContext, "", format, null);
        System.out.println("result == " + result);

        // all clean-up code omitted for simplicity
    }

}

References:

  1. AVSEEK_SIZE documentation
  2. avio_alloc_context() documentation

Additional References: (I do not have enough reputation points for more links but I found these examples critical in helping me so I pasted them in plain text anyway)

  1. Creating Custom FFmpeg IO-Context (CodeProject Example) at: http://www.codeproject.com/Tips/489450/Creating-Custom-FFmpeg-IO-Context

  2. Another example showing the use of write_flag in avio_alloc_context() at: https://www.ffmpeg.org/doxygen/2.5/avio_reading_8c-example.html#a20

Upvotes: 8

Ronald S. Bultje
Ronald S. Bultje

Reputation: 11174

Your seek code needs to handle AVSEEK_SIZE as whence, and your read should return 0 on EOF ("Upon reading end-of-file, zero is returned." - literal quote from man 2 read), not AVERROR_EOF.

Upvotes: 1

Related Questions