JavaCV:avformat_open_input() 挂起(不是网络,而是使用自定义 AVIOContext)

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

我正在使用自定义 AVIOContext 将 FFMpeg 与 java IO 桥接。函数 avformat_open_input() 从不 returns。我在网上搜索过类似的问题,都是网络问题或者服务器配置错误导致的。但是,我根本没有使用网络,正如您在下面的小程序中看到的那样:

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
    }

}

下面是我的示例控制台输出:

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.

我检查了字节总和,它对应于文件大小; EOF 也被击中,这意味着文件被完全读取。其实我有点怀疑为什么 avformat_open_input() 甚至会读取整个文件并且仍然没有返回?我所做的一定有问题。任何专家都可以阐明一些观点或指出正确的方向吗?我是 javacvffmpeg 的新手,尤其是 Buffer 和东西的编程新手。欢迎任何帮助、建议或批评。提前致谢。

您的搜索代码需要处理 AVSEEK_SIZE 作为来源,并且您的读取应该 return EOF 上的 0("Upon reading end-of-file, zero is returned." - man 2 read 的字面引述),而不是 AVERROR_EOF.

好的,现在我找到问题了。我误解了 文档并且忽略了 我找到的大部分示例。我的错。

根据 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 
)

第三个参数write_flag按以下方式使用:

write_flag - 如果缓冲区应该是可写的,则设置为 1,否则设置为 0

其实就是说如果AVIOContext是为了数据输出(即写入),那么write_flag就应该设置成1。否则,如果上下文是用于数据输入(即读取),则应设置为0.

在我 post 编辑的问题中,我将 1 作为 write_flag 传递,这导致了阅读时出现问题。传递 0 反而解决了问题。

后来我重新阅读了我找到的所有例子,阅读时所有avio_alloc_context()调用都使用0,而不是1。所以这进一步说明了我遇到问题的原因。

总结一下,我将post修正了问题的修改后的代码作为以后的参考。

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
    }

}

参考文献:

  1. AVSEEK_SIZE documentation
  2. avio_alloc_context() documentation

其他参考资料:(我没有足够的信誉点数来获取更多链接,但我发现这些示例对我的帮助很重要,所以我还是以纯文本形式粘贴了它们)

  1. 在以下位置创建自定义 FFmpeg IO 上下文(CodeProject 示例): http://www.codeproject.com/Tips/489450/Creating-Custom-FFmpeg-IO-Context

  2. 显示 avio_alloc_context()write_flag 用法的另一个例子: https://www.ffmpeg.org/doxygen/2.5/avio_reading_8c-example.html#a20