JavaCV:avformat_open_input() 挂起(不是网络,但带有自定义 AVIOContext)

Yun*_*Hai 0 ffmpeg javacv

我正在使用自定义 AVIOContext 将 FFMpeg 与 java IO 桥接。该函数avformat_open_input()永远不会返回。我在网上搜索过类似的问题,都是由网络故障或服务器配置错误引起的。但是,我根本没有使用网络,正如您在以下小程序中看到的那样:

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
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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.
Run Code Online (Sandbox Code Playgroud)

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

Yun*_*Hai 6

好的,现在我找到了问题所在。我误解了文档并忽略了我发现的大多数示例。我的错。

根据 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 
)
Run Code Online (Sandbox Code Playgroud)

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

write_flag - 设置为1缓冲区是否可写,0否则设置。

实际上,这意味着如果AVIOContext是用于数据输出(即写入),write_flag则应设置为1。否则,如果上下文用于数据输入(即读取),则应将其设置为0

在我发布的问题中,我通过1write_flag,它在阅读时导致了问题。通过0而不是解决问题。

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

总而言之,我将发布修订后的代码并纠正问题,以供将来参考。

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
    }

}
Run Code Online (Sandbox Code Playgroud)

参考:

  1. AVSEEK_SIZE 文件
  2. avio_alloc_context() 文件

其他参考资料:(我没有足够的信誉点来获得更多链接,但我发现这些示例对我的帮助至关重要,因此无论如何我还是将它们粘贴为纯文本)

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

  2. 另一个示例显示了write_flagavio_alloc_context()at 中的使用: https://www.ffmpeg.org/doxygen/2.5/avio_reading_8c-example.html#a20